# Abrova docs (fa) https://abrova.ir — backend services for app developers in Iran. ## ابرووا تریس - SDK فلاتر: https://abrova.ir/docs/trace/flutter-sdk از دستیار هوش مصنوعی استفاده می‌کنید؟ از دکمه‌ی **Copy page for AI** در بالای همین صفحه استفاده کنید و متن را در گفت‌وگو جای‌گذاری کنید. [دستیارهای هوش مصنوعی](/docs/trace/ai-assistants) را ببینید. ### پلتفرم‌های پشتیبانی‌شده - **Flutter Web** — پشتیبانی کامل همراه با گرفتن خطاهای JS - **افزونه‌ی Chrome** — بهینه‌شده برای محیط افزونه - **Android** — خطاهای Dart به‌علاوه‌ی ثبت کرش نیتیو (SDK نیتیو ابرووا تریس همراه پکیج است) - **iOS** — خطاهای Dart به‌علاوه‌ی ثبت کرش نیتیو (SDK نیتیو ابرووا تریس همراه پکیج است) - **دسکتاپ** — Windows، macOS و Linux (فقط خطاهای Dart؛ کرش نیتیو ثبت نمی‌شود)

نصب

پکیج از Abrova Center می‌آید، پس به هیچ مخزن پکیج دیگری نیاز ندارید. دو مرحله دارد: پروژه را به مخزن ابرووا وصل کنید و بعد نصب کنید. **۱. پکیج را از مخزن ابرووا اضافه کنید** در `pubspec.yaml`: ```yaml # pubspec.yaml dependencies: abrova_trace_flutter_sdk: hosted: https://center.abrova.ir/api/packages/abrova/pub version: ^0.2.6 ``` **۲. منبع SDKهای نیتیو را اضافه کنید.** این پلاگین برای ثبت کرش از SDKهای نیتیو Android و iOS استفاده می‌کند. در Android، مخزن Maven ابرووا را در `android/build.gradle` اضافه کنید (یا در `android/settings.gradle`، هر جا که پروژه‌ی شما مخزن‌ها را فهرست می‌کند): ```groovy allprojects { repositories { google() mavenCentral() maven { url "https://center.abrova.ir/api/packages/abrova/maven" } } } ``` در iOS، این خط را داخل target اپ در `ios/Podfile` اضافه کنید: ```ruby pod 'AbrovaTrace', :git => 'https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git', :tag => '0.2.4' ``` سپس نصب کنید: ```bash flutter pub get ``` **پیش‌نیازها**: Dart SDK نسخه‌ی 3.8.1 یا بالاتر. Android: نسخه‌ی API 19 (Android 4.4) یا بالاتر. iOS: نسخه‌ی 12.0 یا بالاتر، ساخته‌شده با Xcode 15.3+ (Swift 5.10). در وب، خطاهای Dart، پیام‌ها، لاگ‌ها و عملکرد کار می‌کنند؛ ثبت کرش نیتیو فقط در Android و iOS است. از کلید عمومی پروژه، یعنی `ab_live_…`، در کنسول ابرووا استفاده کنید. قرار دادن آن داخل اپ امن است. هرگز کلید سرور `ab_srv_…` را در اپ قرار ندهید.

شروع سریع

#### راه‌اندازی پایه ```dart import 'package:flutter/material.dart'; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize AbrovaTrace await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', )); runApp(MyApp()); } ``` #### روش پیشنهادی: راه‌اندازی با Zone (گرفتن بهتر خطاها) با initWithZone خطاهای async هم گرفته می‌شوند که در غیر این صورت از دست می‌روند. این روش پیشنهادی برای اپ‌های production است. ```dart import 'package:flutter/material.dart'; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; void main() async { // IMPORTANT: Initialize AbrovaTrace FIRST, before WidgetsFlutterBinding await AbrovaTrace.initWithZone( AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', captureUncaughtErrors: true, ), () async { // Move all initialization inside this callback WidgetsFlutterBinding.ensureInitialized(); // Any other async initialization goes here // await Firebase.initializeApp(); // await setupDatabase(); runApp(MyApp()); }, ); } ```

ثبت خطاها

#### ثبت Exceptionها ```dart try { riskyOperation(); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Failed to perform risky operation', extra: {'user_id': 'user123'}, ); } ``` #### ثبت پیام‌ها ```dart await AbrovaTrace.captureMessage( 'User completed onboarding', extra: {'step': 'welcome_screen'}, ); // With a level and tags await AbrovaTrace.captureMessage( 'API rate limit approaching', level: MessageLevel.warning, tags: {'feature': 'sync'}, ); ``` پیام‌ها در کنسول، در بخش **ابرووا تریس › پیام‌ها**، همراه با سطح، پلتفرم، داده‌های اضافه و breadcrumbها نمایش داده می‌شوند. پیام‌ها هیچ‌وقت مشکل (issue) نمی‌سازند. سطح‌ها: `MessageLevel.debug`، `info` (پیش‌فرض)، `warning` و `error`. پیام‌ها را برای چیزهایی به کار ببرید که اپ عمداً گزارش می‌دهد («پرداخت شروع شد»، «همگام‌سازی تمام شد»)؛ برای داده‌های عیب‌یابی پرحجم از لاگ‌ها استفاده کنید. #### زمینه‌ی دلخواه برای خطا ```dart await AbrovaTrace.captureException( exception, extra: { 'user_id': 'user123', 'feature': 'shopping_cart', 'action': 'checkout', 'cart_items': 5, 'total_amount': 99.99, }, ); ```

اطلاعات کاربر

خطاها و لاگ‌ها را به کاربر مشخصی نسبت دهید و metadata دلخواه اضافه کنید. اگر از [Abrova Auth](/docs/auth/flutter) استفاده می‌کنید، شناسه‌ی کاربرِ واردشده را بدهید. #### تعیین شناسه‌ی کاربر ```dart // Set after user login AbrovaTrace.setUserId('user_123'); // Get current user ID final userId = AbrovaTrace.getUserId(); ``` #### داده‌ی اضافه‌ی دلخواه ```dart // Set individual extra AbrovaTrace.setExtra('subscription', 'premium'); // Set multiple extras at once AbrovaTrace.setExtras({ 'subscription': 'premium', 'feature_flags': 'dark_mode,beta', }); // Read extra data final sub = AbrovaTrace.getExtra('subscription'); final allExtras = AbrovaTrace.getExtras(); // Clear all extras AbrovaTrace.clearExtras(); ``` #### تگ‌ها ```dart // Set individual tag AbrovaTrace.setTag('team', 'checkout'); // Set multiple tags at once AbrovaTrace.setTags({ 'team': 'checkout', 'module': 'payments', }); // Read tags final team = AbrovaTrace.getTag('team'); final allTags = AbrovaTrace.getTags(); // Clear all tags AbrovaTrace.clearTags(); ``` Breadcrumbها کارهای کاربر و رویدادهای منتهی به خطا را ثبت می‌کنند تا بفهمید پیش از کرش چه اتفاقی افتاده است. ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // User action breadcrumb AbrovaTrace.addUserBreadcrumb('Added item to cart', data: { 'product_id': 'abc', 'quantity': 2, }); // Navigation breadcrumb AbrovaTrace.addNavigationBreadcrumb('HomeScreen', 'ProductDetail'); // HTTP request breadcrumb AbrovaTrace.addHttpBreadcrumb('POST', 'https://api.example.com/orders', statusCode: 201); // Generic breadcrumb AbrovaTrace.addBreadcrumb( 'Order placed', type: BreadcrumbType.info, data: {'order_id': '12345', 'total': 99.99}, ); // Clear all breadcrumbs AbrovaTrace.clearBreadcrumbs(); ```

ثبت خودکار لمس‌ها (Breadcrumbهای ژست)

هر لمس کاربر را خودکار به‌صورت breadcrumb ثبت کنید. این قابلیت روی همه‌ی targetهای Flutter کار می‌کند — وب، Android، iOS، macOS، Windows و Linux — چون به pointer router خود فریم‌ورک وصل می‌شود، نه به کد پلتفرم. وقتی خطایی رخ می‌دهد، جزئیات مشکل نشان می‌دهد کاربر درست پیش از آن روی کدام ویجت زده است — معمولاً همین برای بازتولید باگ کافی است، حتی بدون stack trace کامل. این مخصوصاً در Flutter Web مفید است، چون به‌خاطر inline شدن کد توسط dart2js ممکن است stack traceهای production فقط تا حدی symbolicate شوند. #### فعال‌سازی یک بار بعد از init و بعد از `WidgetsFlutterBinding.ensureInitialized()` صدا بزنید. این متد idempotent است — چند بار صدا زدنش مشکلی ندارد. ```dart await AbrovaTrace.initWithZone( AbrovaTraceConfig(apiKey: 'ab_live_...'), () async { WidgetsFlutterBinding.ensureInitialized(); // Enable automatic tap breadcrumbs AbrovaTrace.enableGestureBreadcrumbs(); runApp(MyApp()); }, ); ``` #### چه چیزی ثبت می‌شود هر لمس روی یک ویجت تعاملیِ شناخته‌شده، breadcrumbی مثل این می‌سازد: ```json { "message": "Tap on ElevatedButton: \"Pay\"", "type": "user", "timestamp": "2026-05-15T14:32:18.421Z", "data": { "action": "tap", "widget": "ElevatedButton", "label": "Pay", "key": "checkout-pay-button", "path": "CheckoutPage > Scaffold", "route": "/checkout", "position": { "x": 187, "y": 614 } } } ``` فیلد `route` وقتی پر می‌شود که `AbrovaTraceNavigatorObserver()` را به `navigatorObservers` اپ اضافه کنید (این observer breadcrumbهای جابه‌جایی بین صفحه‌ها را هم خودکار ثبت می‌کند). #### ویجت‌های شناخته‌شده ردیاب این نوع ویجت‌ها را بدون تنظیم اضافه تشخیص می‌دهد (نوع‌های مشخص‌تر اولویت دارند): - ElevatedButton - TextButton - OutlinedButton - FilledButton - IconButton - FloatingActionButton - Switch - Checkbox - Radio - Slider - PopupMenuButton - DropdownButton - InkWell - InkResponse - GestureDetector - CupertinoButton #### نکته: به ویجت‌های مهم ValueKey بدهید Breadcrumb برچسب را به این ترتیب پیدا می‌کند: فرزند `Text` ← `Semantics.label` ← `Tooltip.message` ← `Icon.semanticLabel`. برای ویجت‌های بدون متن (مثل `IconButton`) یک `ValueKey` اضافه کنید تا بتوانید آن را در کدتان جست‌وجو کنید: ```dart IconButton( key: const ValueKey('cart-remove-row-2'), // ← surfaces in breadcrumb icon: const Icon(Icons.delete), onPressed: _removeItem, ) ``` #### غیرفعال‌سازی ```dart AbrovaTrace.disableGestureBreadcrumbs(); ``` ردیاب برچسب قابل مشاهده‌ی ویجت را ثبت می‌کند — معمولاً متن دکمه‌ای مثل «Pay» یا «Add to Cart». مقدار فیلدهای فرم، محتوای text field یا هر چیزی که کاربر وارد کرده ثبت نمی‌شود. `ValueKey` علاوه بر برچسب گزارش می‌شود، نه به‌جای آن؛ پس متن حساس را در برچسب ویجت‌های قابل لمس نگذارید.

لاگ

دریافت لاگ ساخت‌یافته با ارسال دسته‌ای خودکار و آگاه از چرخه‌ی حیات اپ موبایل. یک بار فعالش کنید و بعد از هر جای اپ از متدهای سطح‌بندی استفاده کنید: ```dart // Enable logging after AbrovaTrace.init() AbrovaTrace.enableLogging(sourceId: 'my-flutter-app'); AbrovaTrace.info('User login successful', metadata: {'user_id': 'user_123'}); ``` برای جزئیات کامل — ارسال دسته‌ای، سطح‌های لاگ، ارسال در پس‌زمینه، flush دستی و پیکربندی — صفحه‌ی [لاگ در Flutter SDK](/docs/trace/flutter-logging) را ببینید.

پایش عملکرد

زمان پاسخ درخواست‌های HTTP و عملیات دلخواه را برای APM اندازه بگیرید. #### کلاینت HTTP خودکار ```dart // Create an HTTP client that automatically tracks all requests final client = AbrovaTrace.createPerformanceClient(); final response = await client.get(Uri.parse('https://api.example.com/users')); // Duration, status code, and errors are automatically reported ``` متد `createPerformanceClient()` همان گزینه‌های `PerformanceHttpClient` (پایین‌تر) را می‌گیرد و کلید API، آدرس API، environment، release و پلتفرم را از پیکربندی پر می‌کند: `inner`، `addBreadcrumbs` (پیش‌فرض `true`)، `excludedHosts` و `minDurationMs` (پیش‌فرض `0`). #### PerformanceHttpClient کلاس `PerformanceHttpClient` یک `http.BaseClient` (از `package:http`) است که زمان هر درخواست را اندازه می‌گیرد و آن را به‌صورت یک span از نوع `http` گزارش می‌دهد. وقتی می‌خواهید یک کلاینت موجود را بپوشانید یا خودتان آن را بسازید، مستقیم از آن استفاده کنید: ```dart import 'package:http/http.dart' as http; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; final client = PerformanceHttpClient( apiKey: 'ab_live_...', // Required environment: 'production', releaseVersion: '1.0.0', inner: http.Client(), // Default: a new http.Client() addBreadcrumbs: true, // Also add HTTP breadcrumbs (default: true) excludedHosts: {'analytics.example.com'}, // Hosts to skip minDurationMs: 50, // Skip requests faster than this (default: 0) ); final response = await client.get(Uri.parse('https://api.example.com/users')); // Or wrap an existing client final tracked = http.Client().withPerformanceTracking(apiKey: 'ab_live_...'); // Send buffered spans now, and stop the client when done await client.flush(); client.close(); ``` Spanها بافر می‌شوند و در دسته‌های ۱۰تایی یا هر ۱۰ ثانیه ارسال می‌شوند. درخواست‌ها به hostهای ابرووا هیچ‌وقت اندازه‌گیری نمی‌شوند. در آدرس breadcrumbها، پارامترهای حساس query (token، password، api_key و …) با `[REDACTED]` جایگزین می‌شوند. Spanها در بخش **ابرووا تریس › عملکرد** نمایش داده می‌شوند. #### اندازه‌گیری عملیات دلخواه ```dart final result = await AbrovaTrace.trackOperation( 'load_user_profile', () async { final user = await fetchUser(); await cacheUser(user); return user; }, operationType: 'custom', ); ``` #### گزارش دستی span ```dart final start = DateTime.now(); // Report a single performance span await AbrovaTrace.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method: 'GET', url: 'https://api.example.com/products', statusCode: 200, startTime: start, durationMs: 245, ), ); // Report a batch of spans await AbrovaTrace.reportPerformanceSpanBatch([ PerformanceSpan.fromHttpRequest( method: 'GET', url: '/api/users', statusCode: 200, startTime: start, durationMs: 150, ), PerformanceSpan.custom( operation: 'image_processing', startTime: start, durationMs: 800, ), ]); ```

استفاده بر اساس پلتفرم

#### افزونه‌ی Chrome ```dart void main() async { await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', debug: false, // Disable in production extensions )); runApp(MyExtensionApp()); } ``` #### اپ موبایل ```dart void main() async { // Required before init() on Android and iOS WidgetsFlutterBinding.ensureInitialized(); await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', captureUncaughtErrors: true, // Recommended for mobile )); runApp(MyMobileApp()); } ``` #### اپ وب ```dart void main() async { await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', debug: kDebugMode, // Enable debug in development )); runApp(MyWebApp()); } ```

تشخیص کرش

ابرووا تریس برای هر اپ Flutter، فارغ از کتابخانه‌ها یا کد نیتیوی که استفاده می‌کنید، تشخیص کامل خطا فراهم می‌کند. - **Exceptionهای Dart** — به‌صورت لحظه‌ای ثبت می‌شوند - **خطاهای async** — به‌صورت لحظه‌ای ثبت می‌شوند (با initWithZone) - **Exceptionهای پلتفرم** — به‌صورت لحظه‌ای ثبت می‌شوند - **کرش‌های نیتیو** — فقط در iOS و Android؛ در اجرای بعدی اپ گزارش می‌شوند #### تشخیص کرش نیتیو چطور کار می‌کند این پلاگین SDKهای نیتیو ابرووا تریس را برای iOS و Android همراه خود دارد و آن‌ها را هنگام `init()` / `initWithZone()` راه‌اندازی می‌کند: 1. **iOS** — کتابخانه‌ی PLCrashReporter کرش‌های ناشی از signal و Mach exception را روی دیسک ذخیره می‌کند 2. **Android** — یک handler برای exceptionهای مدیریت‌نشده، به‌علاوه‌ی `ApplicationExitInfo` (Android 11 / API 30+) برای کرش‌های نیتیو و ANRها 3. در اجرای بعدی، SDK نیتیو گزارش ذخیره‌شده را می‌خواند و آن را به‌صورت یک مشکل به ابرووا تریس می‌فرستد به کد اضافه‌ای نیاز نیست. کرش‌ها در اجرای *بعدی* ارسال می‌شوند، پس کرش بعد از این‌که کاربر اپ را دوباره باز کند ظاهر می‌شود. متد `AbrovaTrace.close()` لاگ‌های در صف را ارسال می‌کند و SDK را آزاد می‌کند. برای تشخیص کرش لازم نیست. بعد از `close()`، فراخوانی‌هایی مثل `captureException()` خطا می‌دهند تا وقتی که دوباره `init()` را صدا بزنید؛ پس فقط وقتی صدایش بزنید که اپ واقعاً در حال بسته شدن است.

گزینه‌های پیکربندی

```dart AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', // Required environment: 'production', // Default: 'production' release: '1.0.0', // Optional captureUncaughtErrors: true, // Default: true enabled: true, // Default: true debug: false, // Default: false timeout: 30, // Default: 30 seconds sampleRate: 1.0, // 0.0 to 1.0 maxBreadcrumbs: 20, // Default: 20 enableOfflineStorage: true, // Default: true collectDeviceInfo: true, // Default: true logHandler: (level, message, error, stackTrace) {}, // Optional ) ``` #### ذخیره‌سازی آفلاین وقتی `enableOfflineStorage` روشن باشد (پیش‌فرض)، خطاهایی که به‌خاطر آفلاین بودن دستگاه ارسال نمی‌شوند روی دستگاه نگه داشته می‌شوند و بعداً ارسال می‌شوند. فقط خطاها نگه داشته می‌شوند — پیام‌ها، لاگ‌ها و داده‌های عملکرد روی دیسک ذخیره نمی‌شوند. - **خطاهای نیتیو (Android، iOS)** — این گزینه به SDKهای نیتیو همراه پکیج داده می‌شود که تا ۱۰۰ خطای ارسال‌نشده را نگه می‌دارند و بعداً می‌فرستند. به نسخه‌ی 0.2.5 یا بالاتر نیاز دارد. - **خطاهای Dart** — وقتی ارسال با خطای شبکه یا timeout شکست بخورد، خطا در یک فایل ذخیره می‌شود (تا ۱۰۰ مورد؛ قدیمی‌ترها اول حذف می‌شوند) و دفعه‌ی بعد که اپ اجرا شود دوباره ارسال می‌شود؛ هم با `AbrovaTrace.init()` و هم با `AbrovaTrace.initWithZone()` (برای `initWithZone()` نسخه‌ی 0.2.5 یا بالاتر لازم است). خطای ذخیره‌شده‌ای که سرور برای همیشه رد کند، به‌جای تلاش دوباره در هر اجرا، حذف می‌شود؛ اما اگر سرور بخواهد بعداً دوباره تلاش شود (HTTP 408، 429) یا خودش مشکل داشته باشد (5xx)، نگه داشته می‌شود. - **وب** — چیزی ذخیره نمی‌شود. پیش از نسخه‌ی 0.2.6، SDK ممکن بود هنگام شروع، وقتی از `initWithZone()` همراه با `WidgetsFlutterBinding.ensureInitialized()` داخل تابع اپ استفاده می‌کردید، پیام `AbrovaTrace: Failed to initialize offline storage: Binding has not yet been initialized` را چاپ کند. این پیام بی‌خطر بود و نسخه‌ی 0.2.6 دیگر آن را چاپ نمی‌کند: ذخیره‌سازی آفلاین بعد از شروع اپ خودش راه می‌افتد.

اطلاعاتی که همراه خطا ثبت می‌شود

SDK این اطلاعات را خودکار همراه هر خطا ثبت می‌کند: - **پیام خطا** — پیام exception یا پیام دلخواه - **Stack trace** — stack trace کامل برای عیب‌یابی - **پلتفرم** — پلتفرم تشخیص‌داده‌شده (flutter_web، flutter_android و …) - **محیط** — نام محیط (production، development و …) - **Release** — شناسه‌ی نسخه/release اپ - **زمان** — زمان وقوع خطا - **شناسه‌ی کاربر، شناسه‌ی نشست، extraها، تگ‌ها و breadcrumbها** — هر چیزی که روی SDK تنظیم کرده‌اید - **اطلاعات دستگاه** — مدل دستگاه، سیستم‌عامل و نسخه‌ی آن، نسخه و build اپ (وقتی `collectDeviceInfo` روشن است؛ در وب فقط locale و منطقه‌ی زمانی) - **User Agent** — نسخه‌ی SDK، پلتفرم و release (در وب، user agent مرورگر) - **URL** — آدرس صفحه‌ی فعلی (فقط وب) ### نمونه‌ی کامل ```dart import 'package:flutter/material.dart'; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; void main() async { await AbrovaTrace.initWithZone( AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: const String.fromEnvironment('ENV', defaultValue: 'development'), release: '1.0.0', captureUncaughtErrors: true, sampleRate: 1.0, maxBreadcrumbs: 20, enableOfflineStorage: true, debug: true, ), () async { WidgetsFlutterBinding.ensureInitialized(); // Auto-capture taps as breadcrumbs (all platforms) AbrovaTrace.enableGestureBreadcrumbs(); // Enable logging AbrovaTrace.enableLogging( sourceId: 'my-flutter-app', sourceName: 'My Flutter App', ); runApp(MyApp()); }, ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: HomeScreen(), ); } } class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { @override void initState() { super.initState(); AbrovaTrace.setUserId('user_123'); AbrovaTrace.setTag('screen', 'HomeScreen'); AbrovaTrace.addNavigationBreadcrumb('Launch', 'HomeScreen'); } Future _handlePurchase() async { AbrovaTrace.addUserBreadcrumb('Initiated purchase', data: {'product': 'Premium'}); try { await AbrovaTrace.trackOperation('process_purchase', () async { await processPurchase(); }); AbrovaTrace.info('Purchase successful', metadata: {'amount': 99.99}); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Purchase failed', extra: {'user_id': 'user_123', 'screen': 'HomeScreen'}, ); } } Future processPurchase() async { // ... purchase logic } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('My App')), body: Center( child: ElevatedButton( onPressed: _handlePurchase, child: const Text('Purchase'), ), ), ); } } ``` ### توصیه‌ها - SDK نود: https://abrova.ir/docs/trace/nodejs-sdk

نصب

بسته‌ها از Abrova Center نصب می‌شوند. دو مرحله دارد: **۱. رجیستری ابرووا را اضافه کنید** به فایل `.npmrc` در ریشه‌ی پروژه (npm، yarn و pnpm همگی آن را می‌خوانند): ``` # .npmrc @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` **۲. بسته را نصب کنید:** ```bash npm install @abrova-trace/nodejs-sdk # or yarn add @abrova-trace/nodejs-sdk # or pnpm add @abrova-trace/nodejs-sdk ```

شروع سریع

```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); // Initialize AbrovaTrace AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', // project API key serverSecret: process.env.ABROVA_SERVER_SECRET, // ab_srv_... (required) environment: 'production', release: '1.0.0' }); // Capture exceptions try { throw new Error('Something went wrong!'); } catch (error) { AbrovaTrace.captureException(error); } // Capture messages AbrovaTrace.captureMessage('User completed checkout'); ```

ثبت خطاها

#### ثبت exceptionها ```javascript AbrovaTrace.captureException(error, { extra: { userId: 123, feature: 'checkout' } }); ``` #### ثبت پیام‌ها ```javascript AbrovaTrace.captureMessage('Something happened', { level: 'warning', // 'debug' | 'info' (default) | 'warning' | 'error' extra: { context: 'additional info' }, tags: { feature: 'checkout' } }); ``` پیام‌ها در کنسول، در بخش **ابرووا تریس › پیام‌ها** همراه با سطح، پلتفرم، داده‌های اضافه و breadcrumbها نمایش داده می‌شوند. پیام هیچ‌وقت مشکل (issue) نمی‌سازد. سطح `'warn'` به‌صورت `warning`، سطح `'fatal'` به‌صورت `error` و سطح `'trace'` به‌صورت `debug` فرستاده می‌شود؛ هر مقدار دیگری `info` می‌شود. پیام را برای چیزهایی به کار ببرید که برنامه عمداً گزارش می‌دهد («checkout شروع شد»، «همگام‌سازی تمام شد»)؛ برای عیب‌یابی پرحجم از Logs استفاده کنید. Breadcrumbها رویدادهای پیش از خطا را ثبت می‌کنند تا بفهمید قبل از وقوع خطا چه اتفاقی افتاده است. #### Breadcrumb دستی ```javascript AbrovaTrace.addBreadcrumb({ message: 'User clicked checkout', category: 'user-action', level: 'info', data: { cartTotal: 99.99 } }); ``` #### توابع کمکی Breadcrumb ```javascript const { Breadcrumb } = require('@abrova-trace/nodejs-sdk'); // HTTP request breadcrumb AbrovaTrace.addBreadcrumb(Breadcrumb.http('POST', '/api/order', 200, 150)); // Database query breadcrumb AbrovaTrace.addBreadcrumb(Breadcrumb.database('SELECT * FROM users', 25)); // User action breadcrumb AbrovaTrace.addBreadcrumb(Breadcrumb.user('clicked_button', { buttonId: 'submit' })); ```

اطلاعات کاربر

```javascript AbrovaTrace.setUser({ id: 123, email: 'user@example.com', username: 'john_doe' }); ```

Context محدود به یک بلوک

با context محدود می‌توانید کاربر و مقادیر اضافه را فقط برای یک بلوک کد همگام (synchronous) تعیین کنید. وقتی callback برمی‌گردد، کاربر و مقادیر اضافه به حالت قبل برمی‌گردند؛ breadcrumbهایی که داخل بلوک اضافه شده‌اند باقی می‌مانند. ```javascript AbrovaTrace.withScope((scope) => { scope.setUser({ id: 456 }); scope.setExtra('operation', 'bulk_import'); scope.addBreadcrumb({ message: 'Starting import' }); // Errors captured here include this context AbrovaTrace.captureException(new Error('Import failed')); }); // Context is restored after the block ```

یکپارچگی با Express.js

middleware داخلی برای ردیابی خودکار درخواست‌ها و مدیریت خطا در برنامه‌های Express.js. ```javascript const express = require('express'); const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); const app = express(); // Initialize AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', serverSecret: process.env.ABROVA_SERVER_SECRET, environment: process.env.NODE_ENV }); // Add middleware const middleware = AbrovaTrace.expressMiddleware(); app.use(middleware.requestHandler()); // Your routes app.get('/api/users/:id', async (req, res) => { // Your route logic }); // Error handler (must be last) app.use(middleware.errorHandler()); app.listen(3000); ``` middleware مدیریت خطا باید بعد از همه‌ی routeها اضافه شود. Express لازم می‌داند که middlewareهای مدیریت خطا آخر از همه تعریف شوند. پیش از `AbrovaTrace.expressMiddleware()` تابع `AbrovaTrace.init()` را صدا بزنید: middlewareی که پیش از `init()` ساخته شود هیچ کاری نمی‌کند. خطاهایی که `errorHandler()` گزارش می‌دهد شامل متد درخواست، URL، path، route منطبق (مثلاً `/api/users/:id`)، پارامترهای query و route، user agent، آدرس IP و وضعیت پاسخ هستند (اگر `err.status` / `err.statusCode` در محدوده‌ی 4xx یا 5xx باشد همان، وگرنه 500). headerها و body درخواست هرگز فرستاده نمی‌شوند. پارامترهای query و route که نامشان حساس به نظر برسد (password، token، secret، auth، API key، session، cookie، signature، OTP، code) به‌صورت `[REDACTED]` فرستاده می‌شوند. همین قاعده برای آدرس درخواستی که همراه خطاها و breadcrumbها فرستاده می‌شود هم برقرار است: مقدار پارامترهای query حساس در آن با `[REDACTED]` جایگزین می‌شود (مثلاً `/reset?token=[REDACTED]&page=2`)، اما نام پارامترها و بقیه‌ی مقادیر حفظ می‌شوند (از نسخه‌ی 0.2.3).

ردیابی کوئری‌های پایگاه داده

```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); async function executeQuery(sql, params) { const startTime = Date.now(); try { AbrovaTrace.addBreadcrumb({ message: 'Executing query', category: 'database', data: { sql: sql.substring(0, 100) } }); const results = await db.query(sql, params); AbrovaTrace.addBreadcrumb({ message: 'Query completed', category: 'database', data: { duration_ms: Date.now() - startTime } }); return results; } catch (error) { AbrovaTrace.captureException(error, { extra: { sql, duration_ms: Date.now() - startTime } }); throw error; } } ```

فیلتر کردن خطاها

با hook به نام beforeSend می‌توانید رویدادها را پیش از ارسال به ابرووا تریس فیلتر یا اصلاح کنید. این hook برای exceptionها و رویدادهای `captureMessage()` اجرا می‌شود؛ برای حذف یک رویداد `null` برگردانید. برای نادیده گرفتن کامل یک کلاس exception یا یک path درخواست، به‌جای آن از گزینه‌های `ignoredExceptions` و `ignoredPaths` استفاده کنید. ```javascript AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', serverSecret: process.env.ABROVA_SERVER_SECRET, beforeSend(event) { // Don't send network errors if (event.message.includes('ECONNREFUSED')) { return null; } // Add custom data (errors are AbrovaTraceError objects; messages from // captureMessage() arrive as plain objects without setExtra) if (typeof event.setExtra === 'function') { event.setExtra('server_id', process.env.SERVER_ID); } return event; } }); ```

خاموش شدن تمیز

خطاها، پیام‌ها، لاگ‌ها و spanهای عملکرد در پس‌زمینه فرستاده می‌شوند. تابع `flush()` هر چیزی را که هنوز در صف است می‌فرستد؛ پس پیش از پایان پروسه صدایش بزنید: قبل از `process.exit()`، در پایان handler یک تابع serverless و در handlerهای خاموش شدن. ```javascript process.on('SIGTERM', async () => { console.log('Shutting down...'); const sent = await AbrovaTrace.flush(2000); // false if 2 seconds pass before everything is sent await AbrovaTrace.close(); // Sends buffered logs and performance spans, then resets the SDK process.exit(0); }); ``` تابع `flush(timeout)` یک timeout بر حسب میلی‌ثانیه می‌گیرد (پیش‌فرض: 5000) و یک `Promise` برمی‌گرداند: اگر ارسال در مهلت تمام شود `true` و اگر مهلت زودتر تمام شود `false`. این Promise هیچ‌وقت reject نمی‌شود. `close()` هم یک Promise برمی‌گرداند؛ لاگ‌ها و spanهای عملکردِ در بافر را می‌فرستد، اما منتظر خطاها و پیام‌هایی که هنوز در حال ارسال‌اند نمی‌ماند؛ پس اول `flush()` را صدا بزنید. بعد از `close()`، پیش از استفاده‌ی دوباره از SDK باید `init()` را دوباره صدا بزنید. در یک تابع serverless، پیش از برگشتن handler عمل flush را انجام دهید: ```javascript exports.handler = async (event) => { try { return await processEvent(event); } catch (error) { AbrovaTrace.captureException(error); throw error; } finally { await AbrovaTrace.flush(); } }; ```

Source mapها

اگر از TypeScript، bundlerها (webpack، esbuild) یا Babel استفاده می‌کنید، برای stack trace دقیق source mapها را فعال کنید: ```bash node --enable-source-maps app.js ``` یا از داخل کد برنامه: ```javascript process.setSourceMapsEnabled(true); ```

لاگ‌گیری

لاگ ساختاریافته‌ی سمت سرور، به‌صورت دسته‌ای و با backoff نمایی و بدون هیچ وابستگی. لاگ‌ها به **ابرووا تریس › Logs** می‌روند و از `captureMessage()` (بخش پیام‌ها) جدا هستند. بعد از `init()`، یک بار لاگ‌گیری را فعال کنید و از هر جای برنامه لاگ بنویسید: ```javascript AbrovaTrace.enableLogging({ sourceId: 'api-server' }); AbrovaTrace.info('Server started on port 3000'); AbrovaTrace.warn('Database connection slow', { latency: 2500 }); AbrovaTrace.logError('Failed to process payment', { orderId: '12345' }); ``` برای جزئیات کامل این مسیر — سطح‌های لاگ، یکپارچگی با Express، ارسال دسته‌ای و خاموش شدن تمیز — صفحه‌ی [لاگ‌گیری در SDK برای Node.js](/docs/trace/nodejs-logging) را ببینید.

گزینه‌های پیکربندی

```javascript AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', // Required serverSecret: 'ab_srv_your_secret', // Required environment: 'production', // Default: NODE_ENV or 'development' release: '1.0.0', // Default: npm_package_version or '1.0.0' enabled: true, // Default: true debug: false, // Default: false timeout: 5000, // Default: 5000ms captureUncaughtExceptions: true, // Default: true captureUnhandledRejections: true, // Default: true maxBreadcrumbs: 50, // Default: 50 (max 100) sampleRate: 1.0, // Default: 1.0 (captureException and captureMessage) ignoredExceptions: ['ValidationError'], // Error class, error.name, or RegExp ignoredPaths: ['/health*'], // Express paths not reported beforeSend: (event) => event, // Filter/modify events }); ``` ### آمار SDK ```javascript const stats = AbrovaTrace.getStats(); console.log(stats); // { // isEnabled: true, // breadcrumbCount: 5, // rateLimiter: { totalErrorsInWindow: 3, uniqueErrorsInWindow: 2, totalLimit: 100, errorLimit: 10, windowMs: 60000 }, // config: { environment: 'production', release: '1.0.0', apiKey: 'ab_live_abc1234...' } // } ``` ### پشتیبانی از TypeScript SDK تعریف‌های کامل TypeScript را همراه دارد. هر typeی را که لازم دارید import کنید: ```typescript import AbrovaTrace, { AbrovaTraceInitOptions, AbrovaTraceError, Breadcrumb, BreadcrumbOptions, UserContext } from '@abrova-trace/nodejs-sdk'; ``` ### پیش‌نیازها - **Node.js** — >= 14.0.0 - **Express (اختیاری)** — >= 4.0.0 ### مرجع API #### AbrovaTrace - `init(config)` — راه‌اندازی SDK - `captureException(error, options?)` — ثبت یک خطا - `captureMessage(message, options?)` — ارسال پیام به ابرووا تریس › پیام‌ها (`options`: `level`، `extra`، `tags`) - `addBreadcrumb(breadcrumb)` — افزودن یک breadcrumb - `setUser(user)` — تعیین اطلاعات کاربر - `setRequestContext(context)` — تعیین context درخواست که به رویدادهای ثبت‌شده پیوست می‌شود - `withScope(callback)` — اجرای کد با scope جداگانه - `expressMiddleware()` — گرفتن middleware برای Express.js - `flush(timeout?)` — ارسال خطاها، پیام‌ها، لاگ‌ها و spanهای عملکرد در صف؛ `timeout` بر حسب میلی‌ثانیه (پیش‌فرض: 5000). خروجی `Promise` است: اگر مهلت زودتر تمام شود `false` - `close()` — ارسال لاگ‌ها و spanهای عملکرد و سپس پاک‌سازی (یک Promise برمی‌گرداند) - `getStats()` — گرفتن آمار SDK - `enableLogging(options?)`، `log()`، `trace()`، `logDebug()`، `info()`، `warn()`، `logError()`، `fatal()`، `flushLogs()` — لاگ‌گیری (صفحه‌ی [لاگ‌گیری در SDK برای Node.js](/docs/trace/nodejs-logging) را ببینید) - `trackOperation(name, fn, options?)`، `reportPerformanceSpan(span)`، `reportPerformanceSpanBatch(spans)`، `flushPerformance()` — spanهای عملکرد #### توابع کمکی Breadcrumb - `Breadcrumb.http(method, url, status?, duration?)` — breadcrumb برای درخواست HTTP - `Breadcrumb.database(query, duration, error?)` — breadcrumb برای کوئری پایگاه داده - `Breadcrumb.user(action, data?)` — breadcrumb برای کار کاربر - `Breadcrumb.console(level, message, ...args)` — breadcrumb برای خروجی console - `Breadcrumb.navigation(from, to, method?)` — breadcrumb برای جابه‌جایی بین صفحه‌ها - `Breadcrumb.custom(message, category, data?)` — breadcrumb با دسته‌بندی دلخواه #### Middleware برای Express - `requestHandler()` — middleware ردیابی درخواست را برمی‌گرداند - `errorHandler()` — middleware مدیریت خطا را برمی‌گرداند - `userMiddleware()` — اطلاعات کاربر را از `req.user` (id، email، username) تعیین می‌کند - `transactionMiddleware(name)` — برای یک route نام‌دار، breadcrumbهای شروع و پایان (مدت و وضعیت) اضافه می‌کند ### نمونه‌ی کامل ```javascript const express = require('express'); const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); const { Breadcrumb } = require('@abrova-trace/nodejs-sdk'); // Initialize AbrovaTrace AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', serverSecret: process.env.ABROVA_SERVER_SECRET, environment: process.env.NODE_ENV || 'development', release: '1.0.0', captureUncaughtExceptions: true, captureUnhandledRejections: true, maxBreadcrumbs: 50, debug: process.env.NODE_ENV === 'development', }); const app = express(); // Add AbrovaTrace middleware const middleware = AbrovaTrace.expressMiddleware(); app.use(middleware.requestHandler()); app.use(express.json()); // Set user context from auth app.use((req, res, next) => { if (req.user) { AbrovaTrace.setUser({ id: req.user.id, email: req.user.email, }); } next(); }); app.get('/api/users/:id', async (req, res) => { AbrovaTrace.addBreadcrumb(Breadcrumb.user('view_user', { userId: req.params.id, })); try { const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]); AbrovaTrace.addBreadcrumb(Breadcrumb.database('SELECT * FROM users', 15)); res.json(user); } catch (error) { AbrovaTrace.captureException(error, { extra: { userId: req.params.id }, }); res.status(500).json({ error: 'Internal Server Error' }); } }); // Error handler (must be last) app.use(middleware.errorHandler()); // Graceful shutdown process.on('SIGTERM', async () => { await AbrovaTrace.flush(2000); await AbrovaTrace.close(); process.exit(0); }); app.listen(3000, () => console.log('Server running on port 3000')); ``` ### توصیه‌ها - SDK نکست: https://abrova.ir/docs/trace/nextjs-sdk

نصب

پکیج‌ها از Abrova Center می‌آیند. دو مرحله: **۱. مخزن ابرووا را اضافه کنید** به فایل `.npmrc` در ریشه‌ی پروژه (npm، yarn و pnpm همه آن را می‌خوانند): ``` # .npmrc @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` **۲. پکیج را نصب کنید:** ```bash npm install @abrova-trace/nextjs-sdk # or yarn add @abrova-trace/nextjs-sdk # or pnpm add @abrova-trace/nextjs-sdk ```

شروع سریع

#### App Router (Next.js 13+) فایل `app/providers.tsx` را بسازید: ```tsx 'use client'; import { useEffect } from 'react'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; import { useAbrovaTraceNavigation } from '@abrova-trace/nextjs-sdk/react'; export function Providers({ children }: { children: React.ReactNode }) { useEffect(() => { AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: process.env.NODE_ENV, release: process.env.NEXT_PUBLIC_APP_VERSION, debug: process.env.NODE_ENV === 'development', }); }, []); // Track navigation useAbrovaTraceNavigation(); return <>{children}; } ``` هوک `useAbrovaTraceNavigation()` از `useSearchParams()` استفاده می‌کند. اگر `next build` درباره‌ی نبودن Suspense boundary خطا داد، آن را به‌جای این‌جا از یک کامپوننت کوچک داخل `` صدا بزنید (`NavigationTracker` را در بخش «ثبت جابه‌جایی بین صفحه‌ها» در پایین ببینید). فایل `app/layout.tsx` را به‌روز کنید: ```tsx import { Providers } from './providers'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` #### Pages Router (Next.js 12 و قبل‌تر) فایل `pages/_app.tsx` را به‌روز کنید: ```tsx import { useEffect } from 'react'; import type { AppProps } from 'next/app'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; import { useAbrovaTracePagesRouter } from '@abrova-trace/nextjs-sdk/react'; function MyApp({ Component, pageProps }: AppProps) { useEffect(() => { AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: process.env.NODE_ENV, release: process.env.NEXT_PUBLIC_APP_VERSION, }); }, []); // Track navigation useAbrovaTracePagesRouter(); return ; } export default MyApp; ```

Error Boundary

کامپوننت‌ها را داخل error boundary قرار دهید تا خطاهای React خودکار گرفته و گزارش شوند: #### استفاده‌ی پایه ```tsx import { ErrorBoundary } from '@abrova-trace/nextjs-sdk/react'; export default function Page() { return ( Something went wrong!}> ); } ``` #### همراه با تابع reset ```tsx (

Error: {error.message}

)} >
``` #### همراه با handler خطا ```tsx { console.log('Error occurred:', error); // Custom error handling }} > ``` #### Fallback پیش‌فرض بدون `fallback`، این boundary یک پنل داخلی نمایش می‌دهد: عنوان «Something went wrong»، یک پیام کوتاه و دکمه‌ی **Try again** که boundary را reset می‌کند. از نسخه‌ی 0.1.8، پیام خطا، stack trace و component stack دیگر در production نمایش داده نمی‌شوند: این‌ها فقط وقتی `showErrorDetails` برابر `true` باشد در یک بخش جمع‌شونده‌ی «Error details» نمایش داده می‌شوند، و مقدار پیش‌فرض آن `process.env.NODE_ENV !== 'production'` است (در development نمایش داده می‌شود و در buildهای production پنهان است). اگر `fallback` دلخواه بدهید، همان‌طور که نوشته‌اید نمایش داده می‌شود و `showErrorDetails` روی آن اثری ندارد. ```tsx ``` #### Propها

ثبت دستی خطا

#### با hookها ```tsx import { useAbrovaTrace } from '@abrova-trace/nextjs-sdk/react'; function MyComponent() { const { captureException, captureMessage, setUserId } = useAbrovaTrace(); const handleClick = async () => { try { await riskyOperation(); } catch (error) { captureException(error as Error, 'Failed to complete operation', { userId: user.id, action: 'button_click', }); } }; return ; } ``` #### استفاده‌ی مستقیم از API ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; // Capture exception try { throw new Error('Something broke'); } catch (error) { AbrovaTrace.getInstance()?.captureException(error as Error, 'Custom context', { userId: '123', customData: 'value', }); } // Capture a message (level: 'debug' | 'info' | 'warning' | 'error') AbrovaTrace.getInstance()?.captureMessage( 'User completed checkout', 'info', { orderId: '456' } ); ``` پیام‌ها در کنسول، در بخش **ابرووا تریس › پیام‌ها**، همراه با سطح، پلتفرم، داده‌های اضافه و breadcrumbها نمایش داده می‌شوند. پیام‌ها هیچ‌وقت مشکل (issue) نمی‌سازند. سطح‌ها: `'debug'`، `'info'` (پیش‌فرض)، `'warning'` و `'error'`. پیام‌ها را برای چیزهایی به کار ببرید که اپ عمداً گزارش می‌دهد («پرداخت شروع شد»، «همگام‌سازی تمام شد»)؛ برای داده‌های عیب‌یابی پرحجم از لاگ‌ها استفاده کنید. Breadcrumbها کارهای کاربر را ثبت می‌کنند و زمینه‌ی خطاها را فراهم می‌کنند. جابه‌جایی بین صفحه‌ها (بعد از استفاده از یکی از hookهای navigation) و فراخوانی‌های `console.error` / `console.warn` خودکار ثبت می‌شوند. #### Breadcrumbهای دستی ```typescript import { BreadcrumbService, BreadcrumbType } from '@abrova-trace/nextjs-sdk'; // Generic breadcrumb BreadcrumbService.add('User action', BreadcrumbType.User, { button: 'submit' }); // Specific types BreadcrumbService.addUser('Clicked checkout button'); BreadcrumbService.addHttp('POST', '/api/orders', 200); BreadcrumbService.addState('Cart updated', { items: 3 }); BreadcrumbService.addNavigation('/cart', '/checkout'); ``` #### با hookها ```tsx import { useBreadcrumbs } from '@abrova-trace/nextjs-sdk/react'; function Component() { const { addBreadcrumb, addHttp, addState } = useBreadcrumbs(); const handleSubmit = async (data) => { addBreadcrumb('Form submitted', { formName: 'contact' }); const response = await fetch('/api/contact', { method: 'POST', body: JSON.stringify(data), }); addHttp('POST', '/api/contact', response.status); }; return
...
; } ```

تشخیص کرش

SDK با یک سیستم heartbeat مبتنی بر localStorage، کرش و از کار افتادن تب مرورگر را تشخیص می‌دهد. - **کرش تب** — کرش تب مرورگر را تشخیص می‌دهد - **تب بدون پاسخ** — تب‌های هنگ‌کرده را تشخیص می‌دهد - **بستن اجباری** — حالت‌های بستن اجباری را تشخیص می‌دهد - **گزارش پس از وقوع** — گزارش در اجرای بعدی #### چطور کار می‌کند 1. اپ شروع می‌شود — یک نشانگر کرش در localStorage ساخته می‌شود 2. heartbeat هر ۵ ثانیه اجرا می‌شود 3. اپ به‌طور عادی بسته می‌شود — نشانگر حذف می‌شود 4. اپ کرش می‌کند — نشانگر باقی می‌ماند 5. نشست بعدی — کرش تشخیص داده و گزارش می‌شود

API Routeها و Server Componentها

فراخوانی `init()` در `Providers` / `_app.tsx` فقط در مرورگر اجرا می‌شود، پس روی سرور تا وقتی SDK را آن‌جا هم راه‌اندازی نکنید، `AbrovaTrace.getInstance()` مقدار `null` برمی‌گرداند. یک helper کوچک سمت سرور بسازید (اگر نمونه‌ای از قبل راه‌اندازی شده باشد، `init()` همان را برمی‌گرداند): ```typescript // lib/abrova-trace-server.ts import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; export const trace = AbrovaTrace.getInstance() ?? AbrovaTrace.init({ apiKey: process.env.ABROVA_TRACE_API_KEY!, environment: process.env.NODE_ENV, }); ``` #### API Routeها ```typescript // pages/api/example.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { trace } from '@/lib/abrova-trace-server'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { // Your API logic } catch (error) { trace.captureException(error as Error, 'API Error', { endpoint: '/api/example', method: req.method, }); res.status(500).json({ error: 'Internal Server Error' }); } } ``` #### Server Componentها (App Router) ```tsx // app/page.tsx import { trace } from '@/lib/abrova-trace-server'; export default async function Page() { try { const data = await fetchData(); return
{data}
; } catch (error) { trace.captureException(error as Error, 'Server Component Error'); return
Error loading data
; } } ```

Source Mapها

برای داشتن stack traceهای خوانا، source mapهای production را در `next.config.js` فعال کنید: ```javascript // next.config.js module.exports = { productionBrowserSourceMaps: true, }; ``` بعد از build اپ، فایل‌های `.map` را با [CLI ابرووا تریس](/docs/trace/upload-source-maps) آپلود کنید و همان `release` را بدهید که به `AbrovaTrace.init()` می‌دهید: ```bash npx @abrova-trace/cli sourcemaps upload .next/static --release 1.0.0 --project YOUR_PROJECT_ID --token ab_pat_xxx ```

گزینه‌های پیکربندی

error, // Filter/modify errors });`} />

شناسایی کاربر

```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; // Set user ID after authentication AbrovaTrace.getInstance()?.setUserId('user-123'); // Get user ID const userId = AbrovaTrace.getInstance()?.getUserId(); ``` #### App Router (Next.js 13+) ```tsx 'use client'; import { useAbrovaTraceNavigation } from '@abrova-trace/nextjs-sdk/react'; export function NavigationTracker() { useAbrovaTraceNavigation(); return null; } ``` #### Pages Router ```tsx import { useAbrovaTracePagesRouter } from '@abrova-trace/nextjs-sdk/react'; function MyApp({ Component, pageProps }) { useAbrovaTracePagesRouter(); return ; } ``` #### ثبت دستی ```typescript import { RouterIntegration } from '@abrova-trace/nextjs-sdk'; RouterIntegration.setCurrentRoute('/custom-route'); ```

زمینه و تگ‌ها

metadata ماندگار را به همه‌ی خطاها و پیام‌ها اضافه کنید. تگ‌ها برای فیلتر کردن بر اساس دسته‌اند؛ extraها داده‌ی غنی‌تری برای هر رویدادند. (لاگ‌ها، spanها و Web Vitals این‌ها را همراه ندارند.) #### Extraهای سراسری ```typescript const sdk = AbrovaTrace.getInstance(); sdk?.setExtra('organizationId', 'org-123'); sdk?.setExtra('feature', 'checkout-v2'); sdk?.setExtras({ organizationId: 'org-123', feature: 'checkout-v2', experiment: 'new-flow', }); const orgId = sdk?.getExtra('organizationId'); const allExtras = sdk?.getExtras(); sdk?.clearExtras(); ``` #### تگ‌ها ```typescript const sdk = AbrovaTrace.getInstance(); sdk?.setTag('team', 'payments'); sdk?.setTag('region', 'us-east'); sdk?.setTags({ team: 'payments', version: '2.1' }); const team = sdk?.getTag('team'); const allTags = sdk?.getTags(); sdk?.clearTags(); ``` #### شناسه‌ی نشست هنگام راه‌اندازی SDK یک شناسه‌ی نشست یکتا خودکار ساخته می‌شود (۱۶ بایت تصادفی hex) و به همه‌ی خطاها و پیام‌ها اضافه می‌شود تا در کنسول ابرووا بر اساس نشست گروه‌بندی شوند: ```typescript const sessionId = AbrovaTrace.getInstance()?.getSessionId(); ```

Web Vitals

Core Web Vitals را با کتابخانه‌ی رسمی گوگل، `web-vitals`، اندازه بگیرید (فقط در مرورگر). این معیارها در کنسول ابرووا، در بخش **ابرووا تریس › عملکرد**، کنار spanهای HTTP نمایش داده می‌شوند. ```typescript const sdk = AbrovaTrace.getInstance(); sdk?.enableWebVitals({ reportAllChanges: false, // Only report final values (default) batchInterval: 5000, // Send 5 s after the last metric (default); also sent when the page is hidden }); ``` **معیارهای اندازه‌گیری‌شده:** رویدادهای Web Vitals خودکار نوع دستگاه (mobile/tablet/desktop)، نوع اتصال، آدرس صفحه و اطلاعات route را همراه دارند.

TypeScript

این SDK با TypeScript نوشته شده و تعریف کامل typeها را همراه دارد. وقتی برای اشیای پیکربندی یا payload رویدادها annotation صریح لازم دارید، typeها را import کنید: ```typescript import type { AbrovaTraceConfig, AbrovaTraceError, Breadcrumb, } from '@abrova-trace/nextjs-sdk'; import type { PerformanceSpan, WebVitalsData, LogEntry, LogLevel, LogServiceConfig, } from '@abrova-trace/nextjs-sdk'; ```

لاگ

لاگ ساخت‌یافته‌ی سمت سرور که در App Router، Pages Router و API Routeها کار می‌کند. لاگ از یک نمونه‌ی جداگانه‌ی `LogService` استفاده می‌کند — آن را در `lib/logger.ts` بسازید و از هر جا import کنید: ```typescript import { LogService } from '@abrova-trace/nextjs-sdk'; export const logger = new LogService({ apiKey: process.env.ABROVA_TRACE_API_KEY!, sourceId: 'nextjs-app', platform: 'nextjs', environment: process.env.NODE_ENV, }); logger.log('Users fetched', 'info', { count: 42 }); ``` می‌توانید از طریق نمونه‌ی SDK هم لاگ بفرستید که از پیکربندی `init()` استفاده می‌کند: `AbrovaTrace.getInstance()?.info('Users fetched', { count: 42 })` (جدول «لاگ» را در «مرجع API» پایین ببینید). لاگ‌ها از `captureMessage()` جدا هستند: به‌صورت دسته‌ای ارسال می‌شوند و در بخش **ابرووا تریس › لاگ‌ها** نمایش داده می‌شوند. برای جزئیات کامل — App Router، Pages Router، API Routeها، سطح‌های لاگ و middleware درخواست — صفحه‌ی [لاگ در Next.js SDK](/docs/trace/nextjs-logging) را ببینید. ### مرجع API #### `AbrovaTrace` — راه‌اندازی و چرخه‌ی حیات #### ثبت خطا #### کاربر و نشست #### زمینه‌ی سراسری (extraها) #### تگ‌ها #### پایش عملکرد #### لاگ #### `BreadcrumbService` #### `RouterIntegration` #### `CrashDetector` #### هوک‌های React #### کامپوننت‌های React ### توصیه‌ها - SDK اندروید: https://abrova.ir/docs/trace/android-sdk

نصب

SDK از Abrova Center نصب می‌شود. در دو مرحله: **۱. مخزن ابرووا را اضافه کنید** در `settings.gradle.kts` (یا `settings.gradle`): ```kotlin dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://center.abrova.ir/api/packages/abrova/maven") } } } ``` برای Groovy: `maven { url "https://center.abrova.ir/api/packages/abrova/maven" }` **۲. وابستگی را اضافه کنید** به `build.gradle.kts` برنامه: ```kotlin dependencies { implementation("ir.abrova.trace:abrova-trace-android-sdk:0.2.3") } ``` سپس پروژه را با فایل‌های Gradle همگام (sync) کنید. پیش‌نیازها: Android 4.4 (API 19) یا بالاتر؛ گزارش کرش‌های نیتیو به Android 11 (API 30) یا بالاتر نیاز دارد.

شروع سریع

AbrovaTrace را در کلاس `Application` راه‌اندازی کنید: ```kotlin import android.app.Application import ir.abrova.trace.sdk.AbrovaTrace import ir.abrova.trace.sdk.AbrovaTraceConfig class MyApp : Application() { override fun onCreate() { super.onCreate() val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY") .environment(if (BuildConfig.DEBUG) "development" else "production") .release(BuildConfig.VERSION_NAME) .debug(BuildConfig.DEBUG) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .anrTimeoutMs(5000L) .build() AbrovaTrace.init(this, config) } override fun onTerminate() { AbrovaTrace.close() super.onTerminate() } } ``` متد `onTerminate()` روی دستگاه واقعی هیچ‌وقت صدا زده نمی‌شود؛ پس close() را در Activity اصلی هم صدا بزنید. close() لاگ‌های بافرشده را می‌فرستد، watchdog مربوط به ANR را متوقف می‌کند و کلاینت شبکه را می‌بندد. کلید API باید با `ab_live_` شروع شود؛ در غیر این صورت config خطا می‌دهد. ```kotlin class MainActivity : AppCompatActivity() { override fun onDestroy() { if (isFinishing) { AbrovaTrace.close() // Flush logs, stop the ANR watchdog } super.onDestroy() } } ```

ثبت خطاها

#### ثبت exceptionها ```kotlin try { someRiskyOperation() } catch (e: Exception) { AbrovaTrace.captureException(e) } ``` #### ثبت با اطلاعات تکمیلی ```kotlin AbrovaTrace.captureException( throwable = exception, message = "Failed to load user profile", extra = mapOf("user_id" to userId, "screen" to "ProfileActivity"), tags = mapOf("feature" to "user_profile") ) ``` #### ثبت پیام‌ها ```kotlin import ir.abrova.trace.sdk.models.MessageLevel // Info message AbrovaTrace.captureMessage("User completed onboarding", MessageLevel.INFO) // Warning message AbrovaTrace.captureMessage("API rate limit approaching", MessageLevel.WARNING) // Error message with extra data AbrovaTrace.captureMessage( message = "Payment failed", level = MessageLevel.ERROR, extra = mapOf("amount" to 99.99) ) ``` پیام‌ها در کنسول، در بخش **ابرووا تریس › پیام‌ها**، همراه با سطح، پلتفرم، داده‌های تکمیلی و breadcrumbها نمایش داده می‌شوند. پیام هیچ‌وقت مشکل (issue) نمی‌سازد. سطح‌ها: `MessageLevel.DEBUG`، `INFO` (پیش‌فرض)، `WARNING` و `ERROR`. سطح `FATAL` هم پذیرفته می‌شود و با سطح `error` ذخیره می‌شود. پیام را برای چیزهایی به کار ببرید که برنامه عمداً گزارش می‌دهد («پرداخت شروع شد»، «همگام‌سازی تمام شد»)؛ برای عیب‌یابی پرحجم از لاگ‌ها استفاده کنید. Breadcrumbها کمک می‌کنند بفهمید پیش از وقوع خطا چه اتفاقی افتاده است. برای جابه‌جایی بین Activityها، رفتن برنامه به پیش‌زمینه و پس‌زمینه، و رویدادهای سیستم به‌طور خودکار اضافه می‌شوند. ```kotlin import ir.abrova.trace.sdk.models.BreadcrumbType // User action AbrovaTrace.addUserBreadcrumb("Added item to cart", mapOf("product_id" to "abc")) // Navigation AbrovaTrace.addNavigationBreadcrumb(from = "MainActivity", to = "ProductDetailActivity") // HTTP request AbrovaTrace.addHttpBreadcrumb( method = "POST", url = "https://api.example.com/orders", statusCode = 201, duration = 342L ) // Custom breadcrumb AbrovaTrace.addBreadcrumb("Order placed", BreadcrumbType.INFO, mapOf( "order_id" to "12345", "total" to 99.99 )) ```

یکپارچگی با OkHttp

درخواست‌های HTTP را به‌طور خودکار به‌صورت breadcrumb ثبت کنید و خطاهای HTTP را بگیرید: #### راه‌اندازی پایه ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import okhttp3.OkHttpClient val client = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) .build() ``` #### همراه با ثبت خطا ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import ir.abrova.trace.sdk.network.AbrovaTraceErrorInterceptor import okhttp3.OkHttpClient val client = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) // Adds breadcrumbs .addInterceptor(AbrovaTraceErrorInterceptor()) // Captures HTTP errors .build() ``` کلاس `AbrovaTraceErrorInterceptor` هر پاسخ 5xx را به‌صورت یک **پیام** با سطح `error` می‌فرستد (ابرووا تریس › پیام‌ها). پاسخ‌های 4xx به‌طور پیش‌فرض خاموش‌اند؛ با `AbrovaTraceErrorInterceptor(captureClientErrors = true)` روشنشان کنید (با سطح `warning` فرستاده می‌شوند). خطاهای شبکه (وقتی پاسخی نمی‌رسد) به‌صورت exception ثبت می‌شوند.

تشخیص کرش

ابرووا تریس کرش‌ها را درون خود برنامه تشخیص می‌دهد و روی Android 11 به بالا، از سوابق خروج خود سیستم هم می‌خواند. - **exceptionهای Java/Kotlin**: به‌صورت لحظه‌ای با یک handler برای exceptionهای مدیریت‌نشده (`captureUncaughtExceptions`) گرفته می‌شوند. گزارش پیش از بسته شدن برنامه، روی هر threadی از جمله main thread، فرستاده می‌شود؛ اگر سرور ظرف ۲ ثانیه آن را نپذیرد، روی دستگاه نگه داشته می‌شود و در اجرای بعدی فرستاده می‌شود. به نسخه‌ی 0.2.2 یا جدیدتر نیاز دارد. - **رویدادهای ANR**: یک thread نگهبان (watchdog) تشخیص می‌دهد که main thread بیش از `anrTimeoutMs` مسدود مانده است (`captureAnr`) - **کرش‌های نیتیو**: سیگنال‌هایی مثل SIGSEGV و SIGABRT که در اجرای بعدی گزارش می‌شوند (فقط Android 11 / API 30 به بالا، `captureSignalCrashes`) #### نحوه‌ی کار 1. در هر اجرا (API 30 به بالا)، ابرووا تریس سوابق `ApplicationExitInfo` اندروید را برای برنامه‌ی شما می‌خواند 2. خروج‌هایی که از اجرای قبلی تا حالا با دلیل کرش JVM، کرش نیتیو یا ANR رخ داده‌اند به گزارش کرش تبدیل می‌شوند؛ برای کرش‌های نیتیو، tombstone هم ضمیمه می‌شود 3. زمان آخرین رکورد فرستاده‌شده ذخیره می‌شود تا هر کرش فقط یک بار گزارش شود؛ اگر ارسال ناموفق باشد، در اجرای بعدی دوباره تلاش می‌شود. کرش Java/Kotlinی که handler درون برنامه قبلاً گزارش داده، اینجا نادیده گرفته می‌شود 4. روی Android 10 و پایین‌تر این مرحله کاری انجام نمی‌دهد؛ کرش‌های JVM همچنان با handler درون برنامه گرفته می‌شوند

مانیتورینگ عملکرد (APM)

تأخیر API و عملکرد درخواست‌های HTTP را به‌طور خودکار ردیابی کنید. #### ردیابی خودکار عملکرد HTTP با OkHttp ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient import okhttp3.Request val client = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) // Breadcrumbs .addInterceptor(AbrovaTracePerformanceInterceptor()) // APM tracking .build() // All API calls are automatically tracked! val request = Request.Builder() .url("https://api.example.com/users") .build() client.newCall(request).execute().use { response -> // Duration, status code, and errors are automatically reported val users = response.body?.string() } ``` #### استفاده با Retrofit ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory val okHttpClient = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) .addInterceptor(AbrovaTracePerformanceInterceptor()) .build() val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() val api = retrofit.create(MyApiService::class.java) // All API calls through this Retrofit instance are automatically tracked ``` #### ردیابی عملیات دلخواه ```kotlin import ir.abrova.trace.sdk.AbrovaTrace val result = AbrovaTrace.trackOperation("load_user_profile") { val user = fetchUserFromApi() cacheUserLocally(user) user } ``` #### گزارش دستی span ```kotlin import ir.abrova.trace.sdk.models.PerformanceSpan // HTTP request span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method = "GET", url = "https://api.example.com/products", statusCode = 200, durationMs = 245L, startTime = System.currentTimeMillis() - 245 ) ) // Database query span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.forDbQuery( queryType = "SELECT", tableName = "products", durationMs = 15L, startTime = System.currentTimeMillis() - 15, rowsAffected = 50 ) ) // Custom operation span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.custom( operation = "image_processing", durationMs = 1250L, startTime = System.currentTimeMillis() - 1250, tags = mapOf("format" to "png") ) ) ```

لاگ‌ها

ارسال لاگ ساخت‌یافته با دسته‌بندی (batching) و تلاش مجدد خودکار. یک بار فعالش کنید و سپس از هر جای برنامه از متدهای سطح‌ها استفاده کنید: ```kotlin // Enable logging after AbrovaTrace.init() AbrovaTrace.enableLogging(sourceId = "my-android-app") AbrovaTrace.info("User login successful", mapOf("user_id" to "user_123")) ``` برای جزئیات کامل این مسیر (دسته‌بندی، سطح‌های لاگ، مدیریت چرخه‌ی عمر، ارسال دستی و تنظیمات) صفحه‌ی [لاگ‌ها در Android SDK](/docs/trace/android-logging) را ببینید.

گزینه‌های تنظیمات

```kotlin val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY") .environment("production") // Environment name .release("1.0.0") // App version .debug(false) // Enable debug logging .enabled(true) // Enable/disable SDK .sampleRate(1.0f) // Error sample rate (0.0 - 1.0) .maxBreadcrumbs(20) // Max breadcrumbs to keep .httpTimeout(30) // HTTP request timeout (seconds) .enableOfflineStorage(true) // Keep unsent errors on the device while offline .captureUncaughtExceptions(true) // Catch uncaught exceptions .captureSignalCrashes(true) // Detect native signal crashes .captureAnr(true) // Detect ANR issues .anrTimeoutMs(5000L) // ANR detection timeout .apiUrl("https://trace.abrova.ir") // Server URL (self-hosted override) .build() ``` وقتی خطایی به دلیل قطع بودن اتصال دستگاه فرستاده نشود، SDK آن را در فضای خصوصی برنامه نگه می‌دارد و بعداً می‌فرستد: در اجرای بعدی، و به محض اینکه خطای دیگری با موفقیت فرستاده شود. حداکثر ۱۰۰ خطا نگه داشته می‌شود؛ با رسیدن به این سقف، قدیمی‌ترین‌ها حذف می‌شوند. این فقط شامل خطاهاست: `captureException`، exceptionهای مدیریت‌نشده و گزارش‌های ANR. پیام‌ها، لاگ‌ها و داده‌های عملکرد ذخیره نمی‌شوند. اگر نمی‌خواهید چیزی روی دستگاه بماند، `enableOfflineStorage(false)` را تنظیم کنید.

ProGuard / R8

قوانین ProGuard به‌طور خودکار همراه SDK هستند. اگر به قوانین سفارشی نیاز دارید، آن‌ها را به `proguard-rules.pro` اضافه کنید: ```text -keep class ir.abrova.trace.sdk.** { *; } -keepattributes SourceFile,LineNumberTable -keepattributes *Annotation* ```

سازگاری با دستگاه‌ها

- **Android 4.4 KitKat**: API 19 - **Android 5.0 Lollipop**: API 21 - **Android 8.0 Oreo**: API 26 - **Android 10**: API 29 - **Android 12**: API 31 - **Android 14**: API 34

Context و تگ‌ها

متادیتای ثابت را به همه‌ی خطاها و پیام‌هایی که SDK ثبت می‌کند پیوست کنید (تگ‌ها به spanهای عملکرد هم اضافه می‌شوند). این کار برای فیلتر کردن بر اساس تیم، منطقه، پرچم ویژگی یا گروه آزمایش در کنسول ابرووا مفید است. گزارش‌های ANR و گزارش‌های کرشی که در اجرای بعدی از سیستم خوانده می‌شوند این اطلاعات را ندارند. #### extraهای سراسری extraها جفت‌های کلید/مقدار دلخواه هستند (کلید رشته‌ای، هر مقدار قابل serialize) که به‌طور خودکار در همه‌ی رویدادها ادغام می‌شوند: ```kotlin // Set individual extras AbrovaTrace.setExtra("organizationId", "org-123") AbrovaTrace.setExtra("feature", "checkout-v2") // Set multiple at once AbrovaTrace.setExtras(mapOf( "organizationId" to "org-123", "feature" to "checkout-v2", "experiment" to "new-flow" )) // Clear when no longer needed (e.g. on logout) AbrovaTrace.clearExtras() ``` #### تگ‌ها تگ‌ها جفت‌های کلید/مقدار فقط رشته‌ای هستند که به همه‌ی خطاها و پیام‌ها پیوست می‌شوند: ```kotlin AbrovaTrace.setTag("team", "payments") AbrovaTrace.setTag("region", "us-east") AbrovaTrace.setTags(mapOf("team" to "payments", "version" to "2.1")) AbrovaTrace.clearTags() ``` از **تگ‌ها** برای فیلترهای دسته‌ای با تنوع کم (تیم، منطقه، build flavor) استفاده کنید. از **extraها** برای context غنی‌تر هر رویداد (شناسه‌ی کاربر، بدنه‌ی درخواست، مقادیر محاسبه‌شده). ### مرجع API فهرست کامل متدهای `AbrovaTrace` و `PerformanceSpan`. امضای متدها به سبک Kotlin نوشته شده و پارامترهای اختیاری با `?` مشخص شده‌اند. #### `AbrovaTrace`: راه‌اندازی و چرخه‌ی عمر #### ثبت خطا #### کاربر و نشست #### context سراسری (extraها) #### تگ‌ها #### Breadcrumbها #### مانیتورینگ عملکرد #### لاگ‌ها #### متدهای سازنده‌ی `PerformanceSpan` ### نمونه‌ی کامل ```kotlin import android.app.Application import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import ir.abrova.trace.sdk.AbrovaTrace import ir.abrova.trace.sdk.AbrovaTraceConfig import ir.abrova.trace.sdk.models.MessageLevel import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import okhttp3.OkHttpClient class MyApp : Application() { override fun onCreate() { super.onCreate() val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY") .environment(if (BuildConfig.DEBUG) "development" else "production") .release(BuildConfig.VERSION_NAME) .debug(BuildConfig.DEBUG) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .anrTimeoutMs(5000L) .maxBreadcrumbs(20) .enableOfflineStorage(true) .build() AbrovaTrace.init(this, config) } override fun onTerminate() { AbrovaTrace.close() super.onTerminate() } } class MainActivity : AppCompatActivity() { private val httpClient = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) .build() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AbrovaTrace.setUserId("user_123") AbrovaTrace.addUserBreadcrumb("Viewed home screen") } private fun handlePurchase() { try { processPurchase() AbrovaTrace.captureMessage( message = "Purchase successful", level = MessageLevel.INFO, extra = mapOf("amount" to 99.99) ) } catch (e: Exception) { AbrovaTrace.captureException( throwable = e, message = "Purchase failed", extra = mapOf("user_id" to "user_123", "screen" to "MainActivity") ) } } override fun onDestroy() { if (isFinishing) { AbrovaTrace.close() } super.onDestroy() } private fun processPurchase() { /* ... */ } } ``` ### توصیه‌ها - SDK آی‌اواس: https://abrova.ir/docs/trace/ios-sdk

نصب

#### Swift Package Manager (پیشنهادی) در Xcode از منوی **File → Add Packages** آدرس Abrova Center را وارد کنید: ```text https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git ``` نسخه‌ی `0.2.4` یا بالاتر را انتخاب کنید. یا آن را به `Package.swift` اضافه کنید: ```swift dependencies: [ .package(url: "https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git", from: "0.2.4") ] ``` #### CocoaPods این خط را به `Podfile` اضافه کنید: ```ruby pod 'AbrovaTrace', :git => 'https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git', :tag => '0.2.4' ``` سپس اجرا کنید: ```bash pod install ``` نسخه‌ها از Abrova Center نصب می‌شوند که داخل ایران کار می‌کند. کد منبع روی GitHub است ([Abrova-co/abrova-trace-ios-sdk](https://github.com/Abrova-co/abrova-trace-ios-sdk)) با همان tagهای نسخه؛ پس هرجا GitHub در دسترس باشد `https://github.com/Abrova-co/abrova-trace-ios-sdk.git` هم کار می‌کند.

شروع سریع

#### راه‌اندازی در UIKit ابرووا تریس را در `AppDelegate` راه‌اندازی کنید: ```swift import UIKit import AbrovaTrace @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .debug(false) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .anrTimeoutMs(5000) .build() AbrovaTrace.shared.initialize(config: config) return true } func applicationWillTerminate(_ application: UIApplication) { AbrovaTrace.shared.close() // Optional: the SDK also does this on terminate } } ``` #### راه‌اندازی در SwiftUI ```swift import SwiftUI import AbrovaTrace @main struct MyApp: App { init() { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .debug(false) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .build() AbrovaTrace.shared.initialize(config: config) } var body: some Scene { WindowGroup { ContentView() } } } ```

ثبت خطاها

#### ثبت خطاهای Swift ```swift do { try riskyOperation() } catch { AbrovaTrace.shared.captureError(error) } ``` برای مقادیر `Error` در Swift از `captureError(_:)` و برای `NSException` از `captureException(_:)` استفاده کنید. #### ثبت همراه با context ```swift AbrovaTrace.shared.captureError( error, message: "Failed to load user profile", extra: ["user_id": userId, "screen": "ProfileView"], tags: ["feature": "user_profile"] ) ``` #### ثبت پیام‌ها ```swift // Info message AbrovaTrace.shared.captureMessage("User completed onboarding", level: .info) // Warning message AbrovaTrace.shared.captureMessage("API rate limit approaching", level: .warning) // Error message with extra data AbrovaTrace.shared.captureMessage( "Payment failed", level: .error, extra: ["amount": 99.99] ) ``` پیام‌ها در کنسول، در بخش **ابرووا تریس › پیام‌ها**، همراه با سطح، پلتفرم، داده‌های تکمیلی و breadcrumbها نمایش داده می‌شوند. پیام هیچ‌وقت مشکل (issue) نمی‌سازد. سطح‌ها: `.debug`، `.info` (پیش‌فرض)، `.warning` و `.error`. سطح `.fatal` هم پذیرفته می‌شود و با سطح `error` ذخیره می‌شود. پیام را برای چیزهایی به کار ببرید که برنامه عمداً گزارش می‌دهد («پرداخت شروع شد»، «همگام‌سازی تمام شد»)؛ برای عیب‌یابی پرحجم از لاگ‌ها استفاده کنید. ```swift // User action AbrovaTrace.shared.addUserBreadcrumb("Added item to cart", data: ["product_id": "abc"]) // Navigation AbrovaTrace.shared.addNavigationBreadcrumb(from: "HomeView", to: "ProductDetailView") // HTTP request AbrovaTrace.shared.addHttpBreadcrumb( method: "POST", url: "https://api.example.com/orders", statusCode: 201, duration: 0.342 // seconds (TimeInterval) ) // Custom breadcrumb AbrovaTrace.shared.addBreadcrumb("Order placed", type: .info, data: [ "order_id": "12345", "total": 99.99 ]) ```

ردیابی خودکار HTTP

#### ردیابی سراسری HTTP ردیابی خودکار درخواست‌های URLSession را فعال کنید. هر درخواست به‌صورت یک span عملکرد گزارش می‌شود و به‌عنوان breadcrumb از نوع HTTP اضافه می‌شود: ```swift // Registers AbrovaTraceURLProtocol globally (URLSession.shared and default-configured sessions) AbrovaTrace.shared.enablePerformanceTracking() // Requests are now tracked automatically let (data, response) = try await URLSession.shared.data(from: url) ``` #### ردیابی بر اساس session ```swift // Create a tracked URLSession let config = AbrovaTrace.shared.performanceSessionConfiguration() // Equivalent to: // config.protocolClasses = [AbrovaTraceURLProtocol.self] + (config.protocolClasses ?? []) let session = URLSession(configuration: config) // Requests through this session are tracked let task = session.dataTask(with: url) { data, response, error in // Handle response } task.resume() ``` spanهای عملکردی که enablePerformanceTracking() می‌سازد آدرس کامل درخواست را، همراه با query string، ثبت می‌کنند؛ پس اطلاعات محرمانه را در URL نگذارید یا آن hostها را با setPerformanceExcludedHosts(_:) مستثنا کنید. متد جداگانه‌ی AbrovaTraceHttpBreadcrumbProtocol.enable() breadcrumbهای HTTP را با پنهان کردن پارامترهای حساس query (token، api_key، apikey، key، secret، password، pwd، auth، authorization، access_token، refresh_token، session) اضافه می‌کند و پاسخ‌های 5xx و درخواست‌های ناموفق را به‌صورت پیام گزارش می‌دهد.

تشخیص کرش

ابرووا تریس کرش‌ها را با یک نسخه‌ی تعبیه‌شده از PLCrashReporter ثبت می‌کند: - **NSExceptionهای مدیریت‌نشده**: بلافاصله و پیش از بسته شدن برنامه فرستاده می‌شوند (`captureUncaughtExceptions`) - **رویدادهای ANR**: مسدود ماندن main thread بیش از `anrTimeoutMs` (`captureAnr`) - **کرش‌های نیتیو**: سیگنال‌های POSIX (SIGSEGV، SIGABRT، SIGBUS، SIGILL، SIGFPE، SIGTRAP) و Mach exceptionها (`captureSignalCrashes`) 1. هنگام راه‌اندازی، ابرووا تریس handlerهای async-signal-safe مربوط به PLCrashReporter را نصب می‌کند 2. وقتی برنامه کرش می‌کند، یک گزارش کرش روی دیسک، داخل sandbox برنامه، نوشته می‌شود 3. در اجرای بعدی، `initialize` گزارش در انتظار را می‌خواند و آن را در پس‌زمینه به ابرووا تریس می‌فرستد؛ بنابراین اجرای برنامه هیچ‌وقت منتظر شبکه نمی‌ماند. گزارش فقط پس از تحویل موفق حذف می‌شود؛ اگر سرور یا شبکه خطا بدهد، نگه داشته می‌شود و در اجرای بعدی فرستاده می‌شود. به نسخه‌ی 0.2.3 یا جدیدتر نیاز دارد. کرش‌های نیتیو در اجرای بعدی گزارش می‌شوند، پس فقط وقتی می‌رسند که کاربر دوباره برنامه را باز کند. SDK با دریافت اعلان will-terminate برنامه، خودش AbrovaTrace.shared.close() را صدا می‌زند؛ صدا زدن آن توسط شما اختیاری است و لاگ‌های بافرشده را می‌فرستد.

مانیتورینگ عملکرد (APM)

تأخیر API و عملکرد درخواست‌های HTTP را ردیابی کنید. #### ردیابی خودکار HTTP با URLSession ```swift // Enable performance tracking globally AbrovaTrace.shared.enablePerformanceTracking() // All URLSession requests are now tracked with timing data let (data, response) = try await URLSession.shared.data(from: url) // Method, URL, status code, and duration are automatically reported ``` #### استفاده با Alamofire ```swift import Alamofire // Create a session with AbrovaTrace tracking let configuration = URLSessionConfiguration.default configuration.protocolClasses = [AbrovaTraceURLProtocol.self] + (configuration.protocolClasses ?? []) let session = Session(configuration: configuration) // All requests through this session are tracked session.request("https://api.example.com/users").response { response in // Handle response } ``` #### ردیابی عملیات دلخواه ```swift // Async (iOS 13+); use PerformanceTracker.track(_:) for synchronous work let result = try await PerformanceTracker.trackAsync("load_user_profile") { let user = try await fetchUser() try await cacheUser(user) return user } // Or time it manually let tracker = PerformanceTracker(operation: "image_processing") // ... do work tracker.finish(status: .ok) ``` #### گزارش دستی span ```swift // HTTP request span AbrovaTrace.shared.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method: "GET", url: "https://api.example.com/products", statusCode: 200, durationMs: 245, startTime: Date().addingTimeInterval(-0.245) ) ) // Database query span AbrovaTrace.shared.reportPerformanceSpan( PerformanceSpan.forDbQuery( queryType: "SELECT", tableName: "products", durationMs: 15, startTime: Date().addingTimeInterval(-0.015), rowsAffected: 50 ) ) // Custom operation span AbrovaTrace.shared.reportPerformanceSpan( PerformanceSpan.custom( operation: "image_processing", durationMs: 1250, startTime: Date().addingTimeInterval(-1.25), tags: ["format": "png"] ) ) ```

لاگ‌ها

ارسال لاگ ساخت‌یافته با دسته‌بندی (batching) و تلاش مجدد خودکار. یک بار فعالش کنید و سپس از هر جای برنامه از متدهای سطح‌ها استفاده کنید: ```swift // Enable logging after initialization AbrovaTrace.shared.enableLogging(sourceId: "my-ios-app") AbrovaTrace.shared.info("User login successful", metadata: ["user_id": "user_123"]) ``` برای جزئیات کامل این مسیر (دسته‌بندی، سطح‌های لاگ، مدیریت چرخه‌ی عمر، ارسال دستی و تنظیمات) صفحه‌ی [لاگ‌ها در iOS SDK](/docs/trace/ios-logging) را ببینید.

گزینه‌های تنظیمات

آپلود نمادهای debug (dSYM)

برای دیدن نام فایل‌ها و شماره‌ی خط‌ها در stack traceهای iOS، فایل‌های dSYM برنامه را آپلود کنید. هر build فایل‌های dSYM مخصوص خودش را می‌سازد؛ برای هر نسخه‌ی release یک dSYM جدید آپلود کنید. #### ۱. فایل‌های dSYM را پیدا کنید پس از build برنامه در Xcode، فایل‌های dSYM در خروجی‌های build قرار دارند: ```bash # For debug builds (simulator) ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/YourApp.app.dSYM # For release/archive builds ~/Library/Developer/Xcode/Archives/*.xcarchive/dSYMs/ ``` #### ۲. بسته‌ی dSYM را zip کنید ```bash # Zip the .dSYM bundle cd ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/ zip -r YourApp.dSYM.zip YourApp.app.dSYM ``` #### ۳. از طریق داشبورد آپلود کنید در کنسول ابرووا به **ابرووا تریس › Source maps** بروید و تب **iOS dSYMs** را باز کنید. فایل zip را رها کنید، در صورت تمایل نسخه‌ی release را وارد کنید و روی Upload کلیک کنید. آپلود به نقش member یا بالاتر نیاز دارد و حذف dSYMهای آپلودشده به نقش admin یا owner؛ صفحه‌ی [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. endpoint آپلود dSYM فقط نشست ورود به کنسول را می‌پذیرد (نه کلید API و نه personal access token)، و `@abrova-trace/cli` فقط source map آپلود می‌کند؛ بنابراین dSYMها را از کنسول آپلود کنید. هر بار که برنامه را دوباره build می‌کنید، UUIDهای dSYM تغییر می‌کنند. اگر stack traceها نام توابع را نشان می‌دهند اما فایل و شماره‌ی خط ندارند، باید dSYM مربوط به همان build را آپلود کنید.

پلتفرم‌های پشتیبانی‌شده

- **iOS**: نسخه‌ی 12.0 به بالا - **macOS**: نسخه‌ی 10.14 به بالا - **tvOS**: نسخه‌ی 12.0 به بالا (فقط Swift Package Manager؛ spec مربوط به CocoaPods فقط iOS و macOS را پوشش می‌دهد) از watchOS پشتیبانی نمی‌شود. build کردن SDK به Swift 5.10 یا بالاتر (Xcode 15.3 به بالا) نیاز دارد.

Context و تگ‌ها

متادیتای ثابت را به همه‌ی خطاها و پیام‌هایی که SDK ثبت می‌کند پیوست کنید (گزارش‌های کرش نیتیو و ANR، لاگ‌ها و spanهای عملکرد این اطلاعات را ندارند). این کار برای فیلتر کردن بر اساس تیم، منطقه، پرچم ویژگی یا گروه آزمایش در کنسول ابرووا مفید است. #### extraهای سراسری extraها جفت‌های کلید/مقدار دلخواه هستند (کلید رشته‌ای، هر مقدار قابل serialize) که به‌طور خودکار در همه‌ی رویدادها ادغام می‌شوند: ```swift // Set individual extras AbrovaTrace.shared.setExtra("organizationId", value: "org-123") AbrovaTrace.shared.setExtra("feature", value: "checkout-v2") // Set multiple at once AbrovaTrace.shared.setExtras([ "organizationId": "org-123", "feature": "checkout-v2", "experiment": "new-flow" ]) // Clear when no longer needed (e.g. on logout) AbrovaTrace.shared.clearExtras() ``` #### تگ‌ها تگ‌ها جفت‌های کلید/مقدار فقط رشته‌ای برای دسته‌بندی رویدادها هستند: ```swift AbrovaTrace.shared.setTag("team", value: "payments") AbrovaTrace.shared.setTag("region", value: "us-east") AbrovaTrace.shared.setTags(["team": "payments", "version": "2.1"]) AbrovaTrace.shared.clearTags() ``` از **تگ‌ها** برای فیلترهای دسته‌ای با تنوع کم (تیم، منطقه، build flavor) استفاده کنید. از **extraها** برای context غنی‌تر هر رویداد (شناسه‌ی کاربر، بدنه‌ی درخواست، مقادیر محاسبه‌شده).

یکپارچگی با SwiftUI

SDK بدون مشکل با نقطه‌ی ورود `@main` در SwiftUI کار می‌کند. آن را در `init()` مربوط به struct `App` راه‌اندازی کنید تا خطاها از همان لحظه‌ی ساخته شدن سلسله‌مراتب viewها ثبت شوند: ```swift import SwiftUI import AbrovaTrace @main struct MyApp: App { init() { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_your_api_key") .environment("production") .build() AbrovaTrace.shared.initialize(config: config) } var body: some Scene { WindowGroup { ContentView() } } } ``` در برنامه‌های UIKit، SDK را در `application(_:didFinishLaunchingWithOptions:)` داخل `AppDelegate` راه‌اندازی کنید؛ نمونه‌ی کامل پایین صفحه را ببینید. ### مرجع API فهرست کامل متدهای `AbrovaTrace.shared` و `PerformanceSpan`. selectorها به سبک Swift نوشته شده‌اند و پارامترهای اختیاری در توضیح با `?` مشخص شده‌اند. #### `AbrovaTrace.shared`: راه‌اندازی و چرخه‌ی عمر #### ثبت خطا #### کاربر و نشست #### context سراسری (extraها) #### تگ‌ها #### Breadcrumbها #### مانیتورینگ عملکرد #### لاگ‌ها #### متدهای سازنده‌ی `PerformanceSpan` ### نمونه‌ی کامل ```swift import UIKit import AbrovaTrace @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .debug(false) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .build() AbrovaTrace.shared.initialize(config: config) AbrovaTrace.shared.enablePerformanceTracking() return true } func applicationWillTerminate(_ application: UIApplication) { AbrovaTrace.shared.close() } } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() AbrovaTrace.shared.setUserId("user_123") AbrovaTrace.shared.addNavigationBreadcrumb(from: "Launch", to: "HomeView") } func handlePurchase() { AbrovaTrace.shared.addUserBreadcrumb("Initiated purchase", data: ["product": "Premium"]) do { try processPurchase() AbrovaTrace.shared.captureMessage("Purchase successful", level: .info, extra: [ "amount": 99.99 ]) } catch { AbrovaTrace.shared.captureError( error, message: "Purchase failed", extra: ["user_id": "user_123", "screen": "HomeView"] ) } } func processPurchase() throws { /* ... */ } } ``` ### توصیه‌ها - SDK لاراول: https://abrova.ir/docs/trace/laravel-sdk

نصب

بسته از Abrova Center نصب می‌شود. دو مرحله دارد: **۱. مخزن ابرووا را اضافه کنید** به فایل `composer.json`: ```json "repositories": [ { "type": "composer", "url": "https://center.abrova.ir/api/packages/abrova/composer" } ] ``` **۲. بسته را نصب کنید:** ```bash composer require abrova-trace/laravel-sdk ``` فایل پیکربندی را publish کنید: ```bash php artisan vendor:publish --tag=abrovatrace-config ```

شروع سریع

کلیدها را به `.env` اضافه کنید: ```bash ABROVATRACE_API_KEY=ab_live_your_api_key_here ABROVATRACE_SERVER_SECRET=ab_srv_your_server_secret_here ABROVATRACE_ENVIRONMENT=production ABROVATRACE_RELEASE=1.0.0 ``` این SDK از قابلیت auto-discovery بسته‌ها در Laravel استفاده می‌کند. نیازی به ثبت دستی service provider یا facade نیست. facade آن `AbrovaTrace\Laravel\AbrovaTraceFacade` است (که خودکار با نام مستعار `AbrovaTrace` ثبت می‌شود). تا وقتی هر دو متغیر `ABROVATRACE_API_KEY` و `ABROVATRACE_SERVER_SECRET` تنظیم نشده باشند، SDK غیرفعال می‌ماند.

ثبت خطاها

#### ثبت خودکار همه‌ی exceptionهای مدیریت‌نشده خودکار از طریق exception handler در Laravel ثبت می‌شوند. نیازی به تغییر کد نیست. exception همچنان در لاگ خود برنامه (`laravel.log` یا کانال‌های لاگ تنظیم‌شده) نوشته می‌شود، پس لازم نیست تنظیمات لاگ‌گیری را تغییر دهید. نسخه‌های پیش از 0.2.3، لاگ پیش‌فرض Laravel را برای exceptionهای ثبت‌شده متوقف می‌کردند. #### ثبت دستی ```php use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; // Capture an exception try { riskyOperation(); } catch (\Throwable $e) { AbrovaTrace::captureException($e, [ 'extra' => ['userId' => 123, 'feature' => 'checkout'], ]); } // Capture a message AbrovaTrace::captureMessage('User completed checkout', [ 'extra' => ['orderId' => 'ORD-456'], ]); // With a level and tags ('debug', 'info' (default), 'warning', 'error') AbrovaTrace::captureMessage('Payment retried', [ 'level' => 'warning', 'tags' => ['feature' => 'checkout'], ]); ``` هر exception فقط یک بار گزارش می‌شود. اگر آن را با `captureException()` ثبت کنید و دوباره throw کنید، ثبت خودکار آن را بار دوم نمی‌فرستد (از نسخه‌ی 0.2.3). پیام‌ها در کنسول، در بخش **ابرووا تریس › پیام‌ها** همراه با سطح، پلتفرم، داده‌های اضافه و breadcrumbها نمایش داده می‌شوند. پیام هیچ‌وقت مشکل (issue) نمی‌سازد. سطح `'warn'` به‌صورت `warning`؛ سطح‌های `'fatal'`، `'critical'`، `'alert'` و `'emergency'` به‌صورت `error`؛ سطح `'trace'` به‌صورت `debug` و هر مقدار دیگری به‌صورت `info` فرستاده می‌شود. پیام را برای چیزهایی به کار ببرید که برنامه عمداً گزارش می‌دهد («checkout شروع شد»، «همگام‌سازی تمام شد»)؛ برای عیب‌یابی پرحجم از Logs استفاده کنید. Breadcrumbها رویدادهای پیش از خطا را ثبت می‌کنند. درخواست‌های HTTP و کوئری‌های پایگاه داده خودکار ثبت می‌شوند. #### Breadcrumb دستی ```php use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; use AbrovaTrace\Laravel\Models\Breadcrumb; // HTTP breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::http('POST', '/api/orders', 201, 150.5)); // Database breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::database('SELECT * FROM users WHERE id = ?', 12.5)); // User action breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::user('clicked_checkout', ['cartTotal' => 99.99])); // Custom breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::custom('Cache cleared', 'cache', ['keys' => 15])); ``` SDK به‌طور خودکار breadcrumbهای درخواست و پاسخ HTTP را از طریق middleware و breadcrumbهای کوئری پایگاه داده را از طریق listener رویداد QueryExecuted ثبت می‌کند. نیازی به تنظیم دستی نیست.

اطلاعات کاربر

اطلاعات کاربر خودکار از کاربرِ واردشده و از طریق middleware برداشته می‌شود. می‌توانید آن را دستی هم تعیین کنید: ```php AbrovaTrace::setUser([ 'id' => 123, 'email' => 'user@example.com', 'username' => 'john_doe', ]); ```

Context محدود به یک بلوک

با context محدود می‌توانید اطلاعات کاربر و درخواست را برای هر عملیات جدا کنید. هر دو بعد از آن خودکار به حالت قبل برمی‌گردند (breadcrumbهایی که داخل closure اضافه شده‌اند باقی می‌مانند). closure نمونه‌ی SDK را دریافت می‌کند. ```php AbrovaTrace::withScope(function ($scope) { $scope->setUser(['id' => 456]); $scope->setRequestContext(['operation' => 'bulk_import']); $scope->addBreadcrumb(Breadcrumb::custom('Starting import', 'import')); // Errors captured here include this context AbrovaTrace::captureException(new \RuntimeException('Import failed')); }); // Context is restored after the closure ```

Middleware برای Laravel

middleware خودکار روی گروه‌های web و api ثبت می‌شود و این کارها را انجام می‌دهد: - **ردیابی درخواست** — درخواست ورودی را به‌صورت breadcrumb (متد و path) ثبت می‌کند و context درخواست (متد، URL، path، route، IP، user agent) را تعیین می‌کند - **ردیابی پاسخ** — breadcrumb پاسخ را با کد وضعیت و مدت زمان ثبت می‌کند - **برداشتن اطلاعات کاربر** — اطلاعات کاربر (id، email، name) را خودکار از کاربرِ واردشده تعیین می‌کند (`middleware.track_user`) - **Spanهای عملکرد** — برای هر درخواست یک span عملکرد HTTP می‌سازد (وقتی `performance.enabled` برابر true باشد) برای تغییر pathهای نادیده‌گرفته‌شده، فایل `config/abrovatrace.php` را ویرایش کنید: ```php 'middleware' => [ 'enabled' => true, 'ignored_paths' => [ 'health', 'api/health', '_debugbar/*', 'telescope/*', ], ], ```

ردیابی کوئری‌های پایگاه داده

کوئری‌های پایگاه داده خودکار از طریق رویداد QueryExecuted در Laravel ردیابی می‌شوند (`performance.track_db_queries`). هر کوئری یک breadcrumb می‌شود؛ کوئری‌هایی که مدتشان برابر یا بیشتر از آستانه باشد (پیش‌فرض ۵۰۰ میلی‌ثانیه) span عملکرد هم می‌سازند. ```php // config/abrovatrace.php 'performance' => [ 'enabled' => true, 'track_db_queries' => true, 'slow_query_threshold_ms' => 100, // Span for queries taking 100ms or more ], ```

کانال لاگ در Monolog

لاگ‌ها را با سیستم لاگ‌گیری داخلی Laravel به ابرووا تریس بفرستید: ```php // config/logging.php 'channels' => [ 'abrovatrace' => [ 'driver' => 'custom', 'via' => AbrovaTrace\Laravel\Logging\AbrovaTraceLogChannel::class, ], ], ``` ```php use Illuminate\Support\Facades\Log; Log::channel('abrovatrace')->info('User signed up', ['userId' => 123]); Log::channel('abrovatrace')->error('Payment failed', ['orderId' => 'ORD-456']); Log::channel('abrovatrace')->warning('Rate limit approaching', ['remaining' => 5]); ``` می‌توانید abrovatrace را به یک کانال stack اضافه کنید تا لاگ‌ها هم‌زمان به چند مقصد فرستاده شوند. برای رفتار ارسال دسته‌ای، سطح‌های لاگ و flush دستی، صفحه‌ی [لاگ‌گیری در SDK برای Laravel](/docs/trace/laravel-logging) را ببینید.

ردیابی عملکرد

```php use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; use AbrovaTrace\Laravel\Performance\PerformanceSpan; // Track a custom operation with automatic timing $result = AbrovaTrace::trackOperation('cache.rebuild', function () { return rebuildCache(); }, ['tags' => ['cache' => 'products']]); // Manual performance span AbrovaTrace::reportPerformanceSpan(PerformanceSpan::custom([ 'operation' => 'email.send', 'durationMs' => 250.5, 'startTime' => now()->subMilliseconds(250), 'tags' => ['mailer' => 'smtp'], ])); ```

گزینه‌های پیکربندی

```php // config/abrovatrace.php return [ 'api_key' => env('ABROVATRACE_API_KEY', ''), 'server_secret' => env('ABROVATRACE_SERVER_SECRET', ''), 'enabled' => env('ABROVATRACE_ENABLED', true), 'environment' => env('ABROVATRACE_ENVIRONMENT', env('APP_ENV', 'production')), 'release' => env('ABROVATRACE_RELEASE', '0.1.0'), 'sample_rate' => env('ABROVATRACE_SAMPLE_RATE', 1.0), 'before_send' => null, // set a closure from a service provider 'debug' => env('ABROVATRACE_DEBUG', false), 'timeout' => env('ABROVATRACE_TIMEOUT', 5), 'max_breadcrumbs' => 50, ]; ```

exceptionهای نادیده‌گرفته‌شده

SDK برای exceptionهایی که برنامه‌های Laravel معمولاً نمی‌خواهند به‌عنوان خطا ببینند، پیش‌فرض‌های معقولی دارد. به‌طور پیش‌فرض این‌ها گزارش **نمی‌شوند**: - `AuthenticationException` — درخواست‌های بدون احراز هویت - `ValidationException` — خطاهای اعتبارسنجی فرم - `NotFoundHttpException` — خطاهای 404 - همه‌ی exceptionهای HTTP با کد 4xx از طریق `config/abrovatrace.php` تغییرشان دهید: ```php 'exception_handler' => [ 'ignored_exceptions' => [ \App\Exceptions\BusinessException::class, ], 'report_4xx' => true, ], ``` فهرست `ignored_exceptions` که تعیین می‌کنید جای فهرست پیش‌فرض را می‌گیرد؛ پس مواردی از پیش‌فرض را که هنوز می‌خواهید نادیده گرفته شوند، در فهرست نگه دارید. پاسخ‌های 4xx خطای سمت کلاینت هستند و ثبت دوباره‌شان به‌عنوان exception، فهرست خطاها را شلوغ می‌کند. اگر واقعاً می‌خواهید ثبت شوند (مثلاً برای تحلیل)، `report_4xx` را `true` کنید. ### مرجع API #### Facade ابرووا تریس - `captureException($e, $options?)` — ثبت یک exception - `captureMessage($msg, $options?)` — ارسال پیام به ابرووا تریس › پیام‌ها (`$options`: `level`، `extra`، `tags`) - `addBreadcrumb($breadcrumb)` — افزودن یک breadcrumb - `setUser($user)` — تعیین اطلاعات کاربر - `setRequestContext($context)` — تعیین context درخواست - `withScope($callback)` — اجرای کد با context جداگانه‌ی کاربر و درخواست - `log($message, $level?, $metadata?)` — ارسال یک لاگ (شناسه‌ی کاربر از `setUser()` گرفته می‌شود) - `trace()`، `logDebug()`، `info()`، `warn()`، `logError()`، `fatal()` — لاگ در یک سطح مشخص (`$message, $metadata?`) - `trackOperation($name, $callback, $options?)` — ردیابی عملیات با زمان‌سنجی خودکار (`$options`: `operationType`، `tags`) - `reportPerformanceSpan($span)` — گزارش یک span عملکرد - `flush()` — ارسال لاگ‌ها و spanهای عملکرد در بافر (هنگام خاموش شدن هم خودکار اجرا می‌شود) - `isEnabled()` — بررسی فعال بودن SDK - `getStats()` — گرفتن آمار SDK #### توابع کمکی Breadcrumb - `Breadcrumb::http($method, $url, $status?, $duration?)` — breadcrumb برای درخواست HTTP - `Breadcrumb::database($query, $duration, $error?)` — breadcrumb برای کوئری پایگاه داده - `Breadcrumb::user($action, $data?)` — breadcrumb برای کار کاربر - `Breadcrumb::navigation($from, $to, $method?)` — breadcrumb برای جابه‌جایی بین صفحه‌ها - `Breadcrumb::custom($message, $category, $data?)` — breadcrumb دلخواه ### پیش‌نیازها - **PHP** — >= 8.1 - **Laravel** — 10.x / 11.x / 12.x - **Guzzle** — >= 7.0 (همراه Laravel نصب است) ### نمونه‌ی کامل ```php // routes/api.php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Log; use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; use AbrovaTrace\Laravel\Models\Breadcrumb; Route::get('/users/{id}', function ($id) { // Add a breadcrumb (DB queries are tracked automatically) AbrovaTrace::addBreadcrumb(Breadcrumb::user('view_user', [ 'userId' => $id, ])); try { $user = User::findOrFail($id); return response()->json($user); } catch (\Throwable $e) { AbrovaTrace::captureException($e, [ 'extra' => ['userId' => $id], ]); return response()->json(['error' => 'User not found'], 404); } }); // Log via Monolog channel Route::post('/orders', function (Request $request) { $order = Order::create($request->validate([ 'total' => 'required|numeric', ])); Log::channel('abrovatrace')->info('Order created', [ 'orderId' => $order->id, 'amount' => $order->total, ]); // Track performance of external API call $result = AbrovaTrace::trackOperation('payment.process', function () use ($order) { return PaymentGateway::charge($order); }); return response()->json($result); }); ``` ### توصیه‌ها - نصب داخل ایران: https://abrova.ir/docs/trace/install-in-iran SDKهای ابرووا از [Abrova Center](https://center.abrova.ir) نصب می‌شوند که داخل ایران است. هر SDK چند پکیج رایج دیگر هم لازم دارد (مثلاً AndroidX، OkHttp یا `http` برای Dart). این‌ها از مخزن‌های عمومی می‌آیند؛ بعضی‌شان به آدرس‌های ایرانی جواب نمی‌دهند یا با قطع اینترنت بین‌الملل از دسترس خارج می‌شوند. این صفحه نشان می‌دهد چطور آن‌ها را از mirrorهای داخل ایران بگیرید. همه‌ی آدرس‌های این صفحه در ۵ آبان ۱۴۰۵ از یک سرور داخل ایران و با همان پکیج‌هایی که SDKهای ابرووا لازم دارند بررسی شده‌اند. این mirrorها را شرکت‌های ایرانی دیگری اداره می‌کنند، نه ابرووا؛ پس ممکن است آدرسشان تغییر کند. | پلتفرم | SDK ابرووا | وابستگی‌ها، داخل ایران | |---|---|---| | Android | Abrova Center | mirror مِیوِن مایکت: پکیج‌های Google و Maven Central و پلاگین Gradle اندروید | | iOS | Abrova Center | لازم نیست: SDK وابستگی دیگری ندارد | | Node.js، Next.js، CLI | Abrova Center | mirror npm رانفلر | | Flutter | Abrova Center | mirror داخل ایران ندارد: pub.dev به آدرس‌های ایرانی جواب نمی‌دهد (پایین‌تر) | | Laravel / PHP | Abrova Center | mirror داخل ایران ندارد: Packagist امروز از ایران در دسترس است |

Android (Gradle)

در `settings.gradle`، mirror ایرانی را پیش از Google و Maven Central بیاورید؛ هم برای پلاگین‌ها و هم برای کتابخانه‌ها: ```groovy pluginManagement { repositories { maven { url "https://maven.myket.ir" } gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositories { maven { url "https://center.abrova.ir/api/packages/abrova/maven" } maven { url "https://maven.myket.ir" } google() mavenCentral() } } ``` Gradle مخزن‌ها را به همین ترتیب امتحان می‌کند؛ پس `google()` و `mavenCentral()` فقط برای پکیجی به کار می‌روند که mirror ندارد. خود Gradle برای هر نسخه یک بار از `services.gradle.org` دانلود می‌شود (`distributionUrl` در `gradle/wrapper/gradle-wrapper.properties`). آن فایل را روی سیستمتان نگه دارید، یا روی سروری که در اختیار دارید بگذارید و `distributionUrl` را به آن اشاره دهید.

iOS

کار اضافه‌ای لازم نیست: SDK آی‌اواس از Abrova Center نصب می‌شود و هر چه لازم دارد همراهش است. خط‌های Swift Package Manager و CocoaPods را در [SDK آی‌اواس](/docs/trace/ios-sdk) ببینید.

Node.js، Next.js و CLI

در فایل `.npmrc` پروژه، mirror ایرانی npm را برای همه‌ی پکیج‌ها و Abrova Center را برای پکیج‌های ابرووا بگذارید: ```ini registry=https://mirror-npm.runflare.com/ @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` سپس مثل همیشه نصب کنید (`npm install`، `pnpm install` یا `yarn`).

Flutter

پکیج Flutter ابرووا از Abrova Center می‌آید. وابستگی‌هایش (`http`، `path_provider`، `device_info_plus` و چند پکیج دیگر) از pub.dev می‌آیند و **pub.dev به آدرس‌های ایرانی جواب نمی‌دهد**. mirror برای pub داخل ایران پیدا نکردیم. راه‌هایی که کار می‌کنند: - `flutter pub get` را یک بار از اتصالی اجرا کنید که به pub.dev می‌رسد. پکیج‌ها در pub cache می‌مانند و بیلدهای بعدی بدون pub.dev کار می‌کنند. - `pubspec.lock` را در git نگه دارید تا همه‌ی سیستم‌ها و CI همان نسخه‌ها را از cache بگیرند. - یا `PUB_HOSTED_URL=https://pub.flutter-io.cn` را تنظیم کنید (mirror عمومی که جامعه‌ی Flutter در چین اداره می‌کند). از ایران جواب می‌دهد، ولی خارج از ایران است؛ پس با قطع اینترنت بین‌الملل کار نمی‌کند.

Laravel و PHP

پکیج ابرووا از Abrova Center می‌آید. وابستگی‌هایش (`guzzlehttp/guzzle` و `illuminate/*`) از Packagist می‌آیند که امروز از ایران جواب می‌دهد؛ mirror داخل ایران برای Packagist پیدا نکردیم. Composer هر چه دانلود کند در cache خودش نگه می‌دارد و `composer.lock` نسخه‌ها را ثابت می‌کند.

وقتی اینترنت بین‌الملل قطع است

- **کار می‌کند:** Abrova Center، mirrorهای مایکت و رانفلر، و هر چه از قبل در cache سیستمتان هست. - **کار نمی‌کند:** pub.dev، Packagist، CocoaPods trunk، GitHub و `services.gradle.org`. فایل‌های lock (`pubspec.lock`، `package-lock.json`، `composer.lock`، `Package.resolved`، `Podfile.lock`) را در git نگه دارید تا یک checkout تازه کمترین دانلود را لازم داشته باشد. - دستیارهای هوش مصنوعی (به‌زودی): https://abrova.ir/docs/trace/ai-assistants ابرووا هنوز سرور مستندات برای دستیارهای هوش مصنوعی ندارد. این صفحه توضیح می‌دهد که این سرور چطور کار خواهد کرد و امروز از چه چیزی می‌توانید استفاده کنید. وقتی آماده شود، دستیار هوش مصنوعی خود را (هر ابزاری که از MCP، استاندارد باز اتصال ابزارهای هوش مصنوعی به منابع بیرونی، پشتیبانی کند) یک بار به آن وصل می‌کنید. از آن به بعد، وقتی از دستیار بخواهید ابرووا تریس را به پروژه‌تان اضافه کند، نسخه‌ی فعلی SDK، دستور نصب و مراحل راه‌اندازی را از همین مستندات می‌خواند و از حافظه‌ی خودش جواب نمی‌دهد. ### آنچه امروز کار می‌کند - **Copy page for AI.** هر صفحه‌ی مستندات یک لینک **Copy page for AI** دارد. پیش از آنکه از دستیار بخواهید کد بنویسد، صفحه را در گفتگو با او جای‌گذاری کنید. - **مستندات متنی.** آدرس `https://abrova.ir/docs/llms.txt` را به دستیار خود بدهید. این آدرس همه‌ی صفحه‌های مستندات را به‌صورت متن ساده فهرست می‌کند؛ قالبی که دستیارها به‌خوبی می‌خوانند. به دستیار بگویید از صفحه‌ای که به او داده‌اید پیروی کند، SDK را همان‌طور که صفحه نشان می‌دهد از Abrova Center نصب کند و از کلیدهای نمونه‌ی مستندات استفاده کند. ### چه کاری خواهد کرد سرور مستندات ابزارهایی فقط‌خواندنی خواهد داشت. دستیار شما خودش آن‌ها را انتخاب می‌کند؛ لازم نیست دستی صدایشان بزنید. نمونه‌هایی از چیزهایی که می‌توانید بپرسید: - **«ابرووا تریس را در این پروژه‌ی Next.js راه‌اندازی کن و source mapها را آپلود کن.»** پاسخ از صفحه‌های [SDK برای Next.js](/docs/trace/nextjs-sdk) و [آپلود source map](/docs/trace/upload-source-maps) می‌آید. - **«ابرووا تریس را به اپ Laravel من اضافه کن و قبل از فراخوانی پرداخت یک breadcrumb ثبت کن.»** پاسخ از صفحه‌های [SDK برای Laravel](/docs/trace/laravel-sdk) و [Breadcrumbها](/docs/trace/breadcrumbs) می‌آید. - **«به cron شبانه‌ام یک check-in اضافه کن و هشدارهای ابرووا تریس را به تلگرام بفرست.»** پاسخ از [مانیتورینگ cron](/docs/trace/cron-monitoring) و [تلگرام](/docs/trace/telegram) می‌آید. - **«لاگ همه‌ی کانتینرهای Docker سرورم را به ابرووا تریس بفرست.»** پاسخ از [ایجنت ابرووا تریس](/docs/trace/agent) می‌آید. - **«کدام کلید ابرووا در اپ قرار می‌گیرد و کدام روی سرور می‌ماند؟»** پاسخ از [کلید سرور و مسیرهای سرور](/docs/trace/server-secret) می‌آید. ### حریم خصوصی - **آنچه می‌خواند:** مستندات عمومی ابرووا، نسخه‌های فعلی SDKها و کلماتی که دستیار شما می‌فرستد (کلمات جستجو، صفحه‌ای که می‌خواهد، نام محصول و پلتفرم). - **آنچه هرگز نمی‌بیند:** کد، فایل‌ها، پروژه‌ی ابرووا، کلیدها یا کاربران شما. ابزارها هیچ ورودی‌ای برای این‌ها ندارند و نمی‌توانند هیچ تنظیمی را تغییر دهند. هرگز کلیدی را در گفتگو با دستیار هوش مصنوعی جای‌گذاری نکنید. دستیار شما کلید نمونه‌ی مستندات (`ab_live_your_api_key`) را می‌نویسد و مقدار واقعی را خودتان در تنظیمات پروژه قرار می‌دهید. کلید سرور (`ab_srv_...`) فقط روی سرور شما و در یک متغیر محیطی جا دارد. اطلاعات بیشتر درباره‌ی دستیارهای هوش مصنوعی و ابرووا: [دستیارهای هوش مصنوعی](/ai-assistants). سؤالی دارید؟ [با ما تماس بگیرید](/contact). - معرفی APM: https://abrova.ir/docs/trace/apm APM ابرووا تریس عملکرد همه‌ی اپلیکیشن‌های شما را به‌صورت لحظه‌ای مانیتور می‌کند. تأخیر درخواست‌های HTTP، کوئری‌های دیتابیس و عملیات سفارشی را با صدک‌های p50، p95 و p99 دنبال کنید. برای اپ‌های وب، Core Web Vitals (یعنی LCP، INP و CLS و همچنین TTFB و FCP) را هم مانیتور کنید. APM بدون هزینه‌ی اضافه جزو ابرووا تریس است. spanها و Web Vitals مشکل جدید حساب نمی‌شوند، پس از سهمیه‌ی رایگان ماهانه یا کیف پول شما کم نمی‌کنند. ### نگهداری داده spanها و Web Vitals به اندازه‌ی دوره‌ی نگهداری داده‌ی پروژه‌ی شما نگه داشته می‌شوند: ۷ روز رایگان، یا ۳۰ روز با ۵۰٬۰۰۰ تومان برای هر پروژه در ماه، هزینه از کیف پول. جزئیات در [قیمت‌ها](/pricing). ### ساختار Performance Span هر عملیاتی که دنبال می‌شود یک Performance Span با این فیلدها می‌سازد: ```typescript interface PerformanceSpan { operation: string; // e.g., "GET /api/users" operationType: 'http' | 'db' | 'custom'; httpMethod?: string; // GET, POST, PUT, DELETE httpUrl?: string; // Full request URL httpStatusCode?: number; // 200, 404, 500, etc. httpHost?: string; // api.example.com durationMs: number; // Request duration startTime: string; // ISO timestamp endTime?: string; // ISO timestamp traceId?: string; // Distributed trace ID spanId?: string; // Unique span ID parentSpanId?: string; // Parent span for nesting platform: string; // nextjs, flutter, web, android, ios, nodejs, laravel environment?: string; // production, staging, dev releaseVersion?: string; // App version tags?: Record; // Custom tags metadata?: Record; // Additional data status: 'ok' | 'error'; errorMessage?: string; } ``` ### شروع سریع برای هر SDK #### Next.js / React ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production', }); // Enable automatic HTTP tracking (intercepts fetch) AbrovaTrace.getInstance()?.enablePerformanceTracking(); // Enable Web Vitals (LCP, INP, CLS, TTFB, FCP) AbrovaTrace.getInstance()?.enableWebVitals(); ``` #### Flutter ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Create a performance-tracking HTTP client final client = PerformanceHttpClient( apiKey: 'ab_live_your_api_key', environment: 'production', ); // All requests are automatically tracked final response = await client.get( Uri.parse('https://api.example.com/users'), ); ``` #### Android (Kotlin) ```kotlin import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient val client = OkHttpClient.Builder() .addInterceptor(AbrovaTracePerformanceInterceptor()) .build() // All requests through this client are automatically tracked ``` #### iOS (Swift) ```swift import AbrovaTrace // Enable automatic URLSession tracking AbrovaTrace.shared.enablePerformanceTracking() // Or use a custom session with tracking let config = AbrovaTrace.shared.performanceSessionConfiguration() let session = URLSession(configuration: config) ``` #### Node.js ```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); // Time any async operation and report it as a span const users = await AbrovaTrace.trackOperation( 'fetch-users', () => db.query('SELECT * FROM users'), { operationType: 'db' } ); ``` ### دیدن داده‌ها در داشبورد بعد از فعال شدن ردیابی، در کنسول ابرووا به **ابرووا تریس → Performance** بروید تا این‌ها را ببینید: - **آمار کلی**: تعداد درخواست‌ها، نرخ خطا و تأخیر p50، p95 و p99 - **تأخیر**: p50، p95 و p99 در طول زمان، به‌همراه تعداد درخواست‌ها در طول زمان با خطاهای روی هم - **عملیات**: هر عملیات با نوع، تعداد درخواست، صدک‌های تأخیر و خطاهایش - کارت‌های **کدهای وضعیت، Web Vitals و درخواست‌های ناموفق** - **فیلتر**: بازه‌ی زمانی (۱ ساعت، ۲۴ ساعت، ۷ روز، ۳۰ روز یا دلخواه)، پلتفرم، محیط و release ### Endpointهای API SDKها spanها را به این endpointها می‌فرستند:
{['Endpoint', 'متد', 'توضیح'].map((h) => ( ))} {[ ['/api/performance/spans', 'POST', 'ارسال یک span'], ['/api/performance/spans/batch', 'POST', 'ارسال دسته‌ای (حداکثر ۵۰)'], ['/api/performance/web-vitals', 'POST', 'ارسال داده‌های Web Vitals'], ].map(([endpoint, method, desc]) => ( ))}
{h}
{endpoint} {method} {desc}
### بهترین روش‌ها - ردیابی تأخیر API: https://abrova.ir/docs/trace/api-latency ردیابی تأخیر API زمان‌بندی همه‌ی درخواست‌های HTTP اپلیکیشن شما را ثبت می‌کند. SDK کلاینت‌های HTTP را به‌طور خودکار ابزارگذاری می‌کند، مدت هر درخواست را می‌سنجد و این داده را برای تحلیل با صدک‌های p50، p95 و p99 به ابرووا تریس می‌فرستد.

Next.js / React

#### ردیابی خودکار رهگیری خودکار fetch را فعال کنید تا همه‌ی درخواست‌های HTTP دنبال شوند: ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production', }); // Enable automatic HTTP tracking // This intercepts window.fetch and reports timing AbrovaTrace.getInstance()?.enablePerformanceTracking({ batchSize: 5, // Send spans in batches of 5 (default) flushInterval: 5000, // Flush at most 5 seconds after a span is buffered (default) }); ``` #### ردیابی دستی عملیات ```typescript const sdk = AbrovaTrace.getInstance(); // Track an async operation const result = await sdk?.trackOperation( 'fetchUserProfile', async () => { const response = await fetch('/api/users/123'); return response.json(); }, { operationType: 'http', tags: { userId: '123' } } ); ``` #### Wrapper برای fetch ```typescript const sdk = AbrovaTrace.getInstance(); // Create a tracked fetch function const performanceFetch = sdk?.createPerformanceFetch() ?? fetch; // Use it like regular fetch — timing is automatically captured const response = await performanceFetch('/api/users'); const data = await response.json(); ``` این wrapper فقط وقتی span می‌فرستد که قبلاً `enablePerformanceTracking()`، `trackOperation()` یا `reportPerformanceSpan()` صدا زده شده باشد. در مرورگر، `enablePerformanceTracking()` خودش همه‌ی `fetch`ها را دنبال می‌کند، پس wrapper را سمت سرور به کار ببرید. #### ارسال دستی span ```typescript import { createHttpSpan, PerformanceSpanFactory } from '@abrova-trace/nextjs-sdk'; // Create and report an HTTP span const span = createHttpSpan({ method: 'GET', url: 'https://api.example.com/users', statusCode: 200, durationMs: 245, startTime: new Date(), }); AbrovaTrace.getInstance()?.reportPerformanceSpan(span); // Or use the factory for other span types const dbSpan = PerformanceSpanFactory.forDbQuery({ queryType: 'SELECT', tableName: 'users', durationMs: 15, startTime: new Date(), }); AbrovaTrace.getInstance()?.reportPerformanceSpan(dbSpan); ```

Flutter

#### کلاینت HTTP عملکرد با PerformanceHttpClient همه‌ی درخواست‌های HTTP خودکار دنبال می‌شوند: ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Create a performance-tracking HTTP client final client = PerformanceHttpClient( apiKey: 'ab_live_your_api_key', environment: 'production', releaseVersion: '1.0.0', addBreadcrumbs: true, // Also add HTTP breadcrumbs excludedHosts: {'analytics.example.com'}, // Hosts to skip (Abrova's own API is always skipped) minDurationMs: 0, // Track all requests (set higher to filter fast ones) ); // All requests are automatically tracked final response = await client.get( Uri.parse('https://api.example.com/users'), ); // Don't forget to flush and close await client.flush(); client.close(); ``` #### ردیابی عملیات سفارشی ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Automatic tracking with closure final result = await PerformanceTracker.track( operation: 'processOrder', apiKey: 'ab_live_your_api_key', block: () async => await orderService.process(orderId), ); // Manual tracking final tracker = PerformanceTracker( operation: 'processPayment', apiKey: 'ab_live_your_api_key', operationType: 'custom', environment: 'production', ); // ... perform operation await tracker.finish(status: SpanStatus.ok); ```

Android (Kotlin)

#### Interceptor عملکرد برای OkHttp ```kotlin import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient import okhttp3.Request val client = OkHttpClient.Builder() .addInterceptor(AbrovaTracePerformanceInterceptor()) // APM tracking .build() // All API calls are automatically tracked val request = Request.Builder() .url("https://api.example.com/users") .build() client.newCall(request).execute().use { response -> // Duration, status code, and errors are automatically reported val users = response.body?.string() } ``` #### همراه با Retrofit ```kotlin import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory val okHttpClient = OkHttpClient.Builder() .addInterceptor(AbrovaTracePerformanceInterceptor()) .build() val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() // All API calls through this Retrofit instance are automatically tracked ``` #### ارسال دستی span ```kotlin import ir.abrova.trace.sdk.AbrovaTrace import ir.abrova.trace.sdk.models.PerformanceSpan // Track a custom operation val result = AbrovaTrace.trackOperation("load_user_profile") { val user = fetchUserFromApi() cacheUserLocally(user) user } // Manual span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method = "GET", url = "https://api.example.com/products", statusCode = 200, durationMs = 245L, startTime = System.currentTimeMillis() - 245 ) ) // Database query span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.forDbQuery( queryType = "SELECT", tableName = "products", durationMs = 15L, startTime = System.currentTimeMillis() - 15, rowsAffected = 50 ) ) ```

iOS (Swift)

#### ردیابی خودکار URLSession ```swift import AbrovaTrace // Enable automatic tracking for all URLSession requests AbrovaTrace.shared.enablePerformanceTracking() // Or create a custom session with tracking let config = AbrovaTrace.shared.performanceSessionConfiguration() let session = URLSession(configuration: config) // All requests through this session are automatically tracked let (data, response) = try await session.data(from: url) ``` #### پیکربندی ```swift // Exclude specific hosts from tracking // (Abrova Trace's own API is always excluded) AbrovaTrace.shared.setPerformanceExcludedHosts(["analytics.example.com"]) // Set minimum duration to track (filter very fast requests) AbrovaTrace.shared.setPerformanceMinDuration(10) // Skip requests shorter than 10ms ``` #### ردیابی دستی ```swift import AbrovaTrace // Track with closure let profile = try await PerformanceTracker.trackAsync("fetchProfile") { await api.fetchProfile() } // Manual tracker let tracker = PerformanceTracker(operation: "processOrder") tracker.setTag("orderId", value: "12345") // ... perform operation tracker.finish(status: .ok) ```

Node.js

#### ردیابی عملیات در SDK مربوط به Node.js ثبت خودکار span برای HTTP وجود ندارد. کاری را که می‌خواهید زمان‌سنجی شود داخل `trackOperation` بگذارید، یا spanها را خودتان با `reportPerformanceSpan` بفرستید: ```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); app.get('/api/users/:id', async (req, res) => { const user = await AbrovaTrace.trackOperation( 'GET /api/users/:id', () => db.users.findById(req.params.id), { operationType: 'db', tags: { route: '/api/users/:id' } } ); res.json(user); }); ``` #### Middleware برای Express.js این middleware هر درخواست (متد، URL، وضعیت و مدت) را به‌صورت یک **breadcrumb** از نوع HTTP ثبت می‌کند که به خطاها ضمیمه می‌شود، نه به‌صورت Performance span: ```javascript const middleware = AbrovaTrace.expressMiddleware(); app.use(middleware.requestHandler()); // Your routes // Error handler (must be last) app.use(middleware.errorHandler()); ``` #### ردیابی کوئری‌های دیتابیس ```javascript const { Breadcrumb } = require('@abrova-trace/nodejs-sdk'); async function executeQuery(sql, params) { const startTime = Date.now(); try { AbrovaTrace.addBreadcrumb(Breadcrumb.database(sql.substring(0, 100), 0)); const results = await db.query(sql, params); AbrovaTrace.addBreadcrumb(Breadcrumb.database( 'Query completed', Date.now() - startTime )); return results; } catch (error) { AbrovaTrace.captureException(error, { extra: { sql, duration_ms: Date.now() - startTime } }); throw error; } } ``` ### نمای داشبورد داشبورد عملکرد داده‌های تأخیر را در این نماها نشان می‌دهد:
{[ ['آمار کلی', ['تعداد درخواست‌ها', 'نرخ خطا', 'تأخیر p50', 'تأخیر p95 / p99']], ['تأخیر', ['p50، p95 و p99 در طول زمان', 'تعداد درخواست‌ها در طول زمان، با خطاهای روی هم']], ['عملیات', ['نام و نوع عملیات', 'تعداد درخواست و صدک‌های تأخیر', 'تعداد خطا', 'کارت‌های کدهای وضعیت، Web Vitals و درخواست‌های ناموفق']], ['فیلتر', ['بازه‌ی زمانی (۱ ساعت، ۲۴ ساعت، ۷ روز، ۳۰ روز یا دلخواه)', 'پلتفرم', 'محیط', 'Release']], ].map(([title, items]) => (

{title}

    {items.map((item) => (
  • • {item}
  • ))}
))}
### بهترین روش‌ها - Core Web Vitals: https://abrova.ir/docs/trace/web-vitals Core Web Vitals مجموعه‌ای از معیارهای تعریف‌شده توسط Google است که تجربه‌ی واقعی کاربر را در سرعت بارگذاری، پاسخ‌گویی و پایداری بصری می‌سنجد. این معیارها مستقیم روی رتبه‌ی SEO و رضایت کاربر اثر می‌گذارند. ابرووا تریس برای سنجش دقیق از کتابخانه‌ی رسمی **web-vitals** گوگل استفاده می‌کند. Web Vitals معیارهای مخصوص مرورگر هستند. فعلاً فقط SDK مربوط به Next.js (`@abrova-trace/nextjs-sdk`) آن‌ها را می‌فرستد. برای اپ‌های موبایل (iOS، Android، Flutter) به‌جای آن از ردیابی تأخیر API استفاده کنید. ### توضیح معیارها

LCP — Largest Contentful Paint

سرعت بارگذاری را می‌سنجد. لحظه‌ای را نشان می‌دهد که بزرگ‌ترین عنصر محتوا (تصویر، ویدیو یا بلوک متن) دیده می‌شود.

خوب
≤2.5s
نیاز به بهبود
2.5–4s
ضعیف
>4s

INP — Interaction to Next Paint

پاسخ‌گویی کلی صفحه را می‌سنجد. همه‌ی تعامل‌ها را زیر نظر می‌گیرد و بدترین تأخیر را گزارش می‌کند.

خوب
≤200ms
نیاز به بهبود
200–500ms
ضعیف
>500ms

CLS — Cumulative Layout Shift

پایداری بصری را می‌سنجد. مجموع همه‌ی جابه‌جایی‌های غیرمنتظره‌ی چیدمان در طول عمر صفحه است.

خوب
≤0.1
نیاز به بهبود
0.1–0.25
ضعیف
>0.25
{[ ['TTFB', 'Time to First Byte', 'زمان پاسخ سرور', '≤800ms'], ['FCP', 'First Contentful Paint', 'سرعت بارگذاری از نگاه کاربر', '≤1.8s'], ].map(([abbr, name, desc, good]) => (

{abbr}

{name}

{desc}

خوب: {good}

))}

Next.js

#### راه‌اندازی پایه ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', }); // Enable Web Vitals tracking // Automatically captures LCP, INP, CLS, TTFB, FCP AbrovaTrace.getInstance()?.enableWebVitals(); ``` #### App Router (Next.js 13+) ```tsx // app/providers.tsx 'use client'; import { useEffect } from 'react'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; export function Providers({ children }: { children: React.ReactNode }) { useEffect(() => { AbrovaTrace.init({ apiKey: process.env.NEXT_PUBLIC_ABROVA_TRACE_KEY!, environment: process.env.NODE_ENV, }); // Enable Web Vitals tracking AbrovaTrace.getInstance()?.enableWebVitals(); // Enable HTTP performance tracking AbrovaTrace.getInstance()?.enablePerformanceTracking(); return () => AbrovaTrace.getInstance()?.close(); }, []); return <>{children}; } ``` #### Pages Router ```tsx // pages/_app.tsx import { useEffect } from 'react'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; function MyApp({ Component, pageProps }) { useEffect(() => { AbrovaTrace.init({ apiKey: process.env.NEXT_PUBLIC_ABROVA_TRACE_KEY!, environment: process.env.NODE_ENV, }); AbrovaTrace.getInstance()?.enableWebVitals(); AbrovaTrace.getInstance()?.enablePerformanceTracking(); return () => AbrovaTrace.getInstance()?.close(); }, []); return ; } export default MyApp; ``` #### گزینه‌های پیشرفته ```typescript // Report all metric changes (not just final values) AbrovaTrace.getInstance()?.enableWebVitals({ reportAllChanges: true, }); // Custom batch interval (default: 5000ms): vitals are sent this long after // the last metric arrives, and also when the page is hidden or unloaded AbrovaTrace.getInstance()?.enableWebVitals({ batchInterval: 10000, // 10 seconds }); ```

ساختار داده

داده‌های Web Vitals با این ساختار ارسال می‌شوند: ```typescript interface WebVitalsData { pageUrl: string; // Full page URL pageRoute?: string; // Route path (e.g., /dashboard) // Core Web Vitals lcpMs?: number; // Largest Contentful Paint cls?: number; // Cumulative Layout Shift inpMs?: number; // Interaction to Next Paint ttfbMs?: number; // Time to First Byte fcpMs?: number; // First Contentful Paint // Context deviceType?: string; // 'mobile' | 'tablet' | 'desktop' connectionType?: string; // from Navigator API (4g, 3g, etc.) sessionId?: string; // Per page-load ID from the Web Vitals tracker (not getSessionId()) platform: 'nextjs'; environment?: string; // production, staging releaseVersion?: string; // App version } ```

نمای داشبورد

در کنسول ابرووا، صفحه‌ی **ابرووا تریس ← Performance** کارت جداگانه‌ای برای Web Vitals دارد: صدک ۷۵ام (p75) هر معیار در بازه‌ی انتخاب‌شده، با رتبه‌ی خوب / نیاز به بهبود / ضعیف بر اساس آستانه‌های بالا.

کارت Core Web Vitals

{[ ['LCP', '1.8s', 'خوب', 'green'], ['INP', '120ms', 'خوب', 'green'], ['CLS', '0.15', 'نیاز به بهبود', 'yellow'], ['FCP', '1.2s', 'خوب', 'green'], ].map(([name, value, status, color]) => (
{name}
{value}
{status}
))}

بهتر کردن امتیازها

بهبود LCP

  • • تصاویر را بهینه و فشرده کنید
  • • برای فایل‌های استاتیک از CDN استفاده کنید
  • • منابع حیاتی را preload کنید
  • • جاوااسکریپتی را که جلوی رندر را می‌گیرد به حداقل برسانید

بهبود INP

  • • taskهای طولانی جاوااسکریپت را بشکنید
  • • محاسبات سنگین را به web worker بسپارید
  • • event handlerها را بهینه کنید
  • • جاوااسکریپت غیرضروری را با تأخیر بارگذاری کنید

بهبود CLS

  • • همیشه برای تصاویر ویژگی‌های اندازه را بگذارید
  • • برای محتوای پویا از قبل جا رزرو کنید
  • • محتوا را بالای محتوای موجود اضافه نکنید
  • • به‌جای تغییر چیدمان از انیمیشن‌های transform استفاده کنید
### بهترین روش‌ها - امتیاز عملکرد: https://abrova.ir/docs/trace/performance-score وضعیت Core Web Vitals اپلیکیشن شما در کارت **Web Vitals** در صفحه‌ی **ابرووا تریس ← Performance** نمایش داده می‌شود. هر معیار با **p75** خودش در بازه‌ی زمانی انتخاب‌شده خلاصه می‌شود و بر اساس آستانه‌های استاندارد Google رتبه‌ی **خوب**، **نیاز به بهبود** یا **ضعیف** می‌گیرد، تا افت عملکرد را با یک نگاه ببینید. کنسول ابرووا فعلاً معیارها را در یک امتیاز ۰ تا ۱۰۰ ترکیب نمی‌کند. به‌جای آن رتبه‌ی هر معیار را بخوانید؛ از معیاری شروع کنید که بدترین رتبه را دارد. Web Vitals مخصوص مرورگر است و از SDK مرورگر می‌آید (در SDK مربوط به Next.js یا وب، `enableWebVitals()` را صدا بزنید). SDKهای موبایل و بک‌اند فقط تأخیر درخواست‌ها را می‌فرستند، پس برای پروژه‌های غیروب این کارت پیام «Web Vitals به SDK مرورگر نیاز دارد» را نشان می‌دهد. ### رتبه‌بندی معیارها p75 هر معیار با آستانه‌های استاندارد Google مقایسه می‌شود: اگر برابر یا کمتر از ستون **خوب** باشد «خوب» است، اگر بیشتر از ستون **ضعیف** باشد «ضعیف» است، و هر مقدار بین این دو «نیاز به بهبود» است.
{['معیار', 'نام', 'خوب', 'ضعیف'].map((h) => ( ))} {[ ['LCP', 'Largest Contentful Paint', '≤2.5s', '>4s'], ['INP', 'Interaction to Next Paint', '≤200ms', '>500ms'], ['CLS', 'Cumulative Layout Shift', '≤0.1', '>0.25'], ['FCP', 'First Contentful Paint', '≤1.8s', '>3s'], ['TTFB', 'Time to First Byte', '≤800ms', '>1.8s'], ].map(([metric, weight, good, poor]) => ( ))}
{h}
{metric} {weight} {good} {poor}
معیارهایی که هنوز داده‌ای برایشان نرسیده نمایش داده نمی‌شوند. ### بازه‌های رتبه ### آنچه در داشبورد می‌بینید کارت Web Vitals در صفحه‌ی عملکرد این‌ها را نشان می‌دهد: - **سرتیتر**: «p75 · N بارگذاری صفحه · فقط SDK مرورگر» - **تفکیک هر معیار**: همه‌ی معیارهای ارسال‌شده با مقدار p75، آیکون رتبه و برچسب - **تقسیم خوب / نیاز به بهبود / ضعیف**: هر بارگذاری صفحه در هر معیار چه رتبه‌ای گرفته است ### خواندن تفکیک ردیف‌های تفکیک نشان می‌دهند *چه چیزی* مشکل دارد. ردیف‌ها این شکلی‌اند: ``` LCP 2840ms ● Needs improvement INP 190ms ● Good CLS 0.08 ● Good FCP 1650ms ● Good TTFB 720ms ● Good ``` آیکون‌های رتبه را مرور کنید: اول ردیف‌های «ضعیف» را درست کنید، بعد «نیاز به بهبود». ### بهترین روش‌ها - امتیاز Apdex: https://abrova.ir/docs/trace/apdex Apdex (**Application Performance Index**) یک شاخص استاندارد صنعتی است که تأخیر را در یک عدد بین **۰٫۰۰ و ۱٫۰۰** خلاصه می‌کند. برخلاف صدک‌ها، نشان می‌دهد چه سهمی از کاربران واقعاً از زمان پاسخ *راضی* بوده‌اند، نه فقط این‌که کندترین درخواست‌ها چقدر کند بوده‌اند. ابرووا تریس این امتیاز را روی همه‌ی performance spanهای بازه‌ی زمانی انتخاب‌شده حساب می‌کند. Apdex مدت درخواست‌ها را می‌سنجد و مکمل Web Vitals است (که رندر فرانت‌اند را می‌سنجد). برای تصویر کامل از تجربه‌ی کاربر هر دو را کنار هم ببینید. ### نحوه‌ی محاسبه هر span نسبت به یک تأخیر هدف **T** (`T = 500ms`) در یکی از این دسته‌ها قرار می‌گیرد:
{['دسته', 'بازه', 'وزن', 'معنی'].map((h) => ( ))} {[ ['راضی (Satisfied)', 'duration ≤ T (≤500ms)', '1.0', 'کاربر پاسخ فوری گرفته است'], ['قابل‌تحمل (Tolerating)', 'T < duration ≤ 4T (500ms-2s)', '0.5', 'کند اما قابل‌استفاده'], ['ناامید (Frustrated)', 'duration > 4T (>2s)', '0.0', 'کاربر احتمالاً رها کرده یا دوباره تلاش کرده است'], ].map(([bucket, range, weight, meaning]) => ( ))}
{h}
{bucket} {range} {weight} {meaning}
سپس امتیاز این‌طور حساب می‌شود: ``` Apdex = (satisfied + tolerating × 0.5) / total ``` ### بازه‌های امتیاز (راهنما) ### چرا Apdex (و نه فقط p95)؟ صدک‌ها به این سؤال جواب می‌دهند که «کندترین درخواست‌ها چقدر کندند؟» و Apdex به این‌که «چند کاربر تجربه‌ی بدی داشته‌اند؟». دو مثال: - **سرویس A**: p95 = 1.8s اما p50 = 50ms. Apdex ≈ 0.93. بیشتر کاربران راضی‌اند؛ فقط تعدادی درخواست کند وجود دارد. - **سرویس B**: p95 = 1.8s و p50 = 1.7s. Apdex ≈ 0.51. *نیمی* از کاربران ناامیدند. p95 یکسان، اما واقعیت کاربر کاملاً متفاوت. Apdex مورد دوم را فوراً نشان می‌دهد. ### خواندن امتیاز Apdex از همان فیلترهای بازه‌ی زمانی، پلتفرم، محیط و release پیروی می‌کند که بقیه‌ی صفحه‌ی **ابرووا تریس ← Performance** دارد. کنار امتیاز، تعداد درخواست‌های راضی، قابل‌تحمل و ناامید پشت آن را هم می‌بینید. اگر در بازه‌ی انتخاب‌شده هیچ spanی نباشد، امتیازی هم نیست. ### تغییر آستانه T روی **500ms** ثابت است، چون با SLA استاندارد وب برای «حس فوری بودن» هم‌خوان است. برای هر پروژه قابل‌تنظیم نیست. اگر کار شما واقعاً کندتر است (مثلاً inference یادگیری ماشین یا transcode ویدیو)، امتیاز بدبینانه به نظر می‌رسد؛ در این حالت روی **روند** امتیاز تمرکز کنید، نه مقدار مطلق آن. ### بهترین روش‌ها - تفکیک پلتفرم: https://abrova.ir/docs/trace/platform-breakdown تفکیک پلتفرم نشان می‌دهد داده‌های عملکرد شما بین SDKها (iOS، Android، وب، Node، Flutter، Laravel) چطور تقسیم شده است، تا سریع ببینید *کدام* پلتفرم ترافیک را می‌سازد و *عملکردش نسبت به بقیه چطور است*. ابرووا تریس هر span را بر اساس فیلد `platform` که هر SDK روی spanهایش می‌گذارد گروه‌بندی می‌کند. اگر p95 کلی اپ بعد از یک release بالا رفت، تفکیک پلتفرم سریع‌ترین راه است تا بفهمید عامل آن آپدیت iOS است، انتشار Android یا build جدید وب. ### آنچه در داشبورد می‌بینید در کنسول ابرووا، در صفحه‌ی **ابرووا تریس → Performance**، با فیلتر **پلتفرم** در نوار ابزار همه‌ی کارت‌ها (آمار، تأخیر، عملیات، کدهای وضعیت، درخواست‌های ناموفق) را به یک پلتفرم محدود کنید. بین پلتفرم‌ها جابه‌جا شوید تا تعداد درخواست، تأخیر و نرخ خطای آن‌ها را کنار هم مقایسه کنید. ### پلتفرم چطور تشخیص داده می‌شود فیلد `platform` را SDK روی هر spanی که می‌فرستد تنظیم می‌کند:
{['SDK', 'پلتفرم ارسالی', 'منبع'].map((h) => ( ))} {[ ['Next.js / Web', 'nextjs', 'ثابت، توسط SDK'], ['Flutter', 'flutter_android / flutter_ios / flutter_web_* …', 'در زمان اجرا از طریق AbrovaTrace.createPerformanceClient / trackOperation تشخیص داده می‌شود؛ یک PerformanceHttpClient مستقل مقدار flutter (یا web) را می‌فرستد مگر این‌که platform را خودتان بدهید، و spanهایی که خودتان برای reportPerformanceSpan می‌سازید فقط همان پلتفرمی را دارند که تنظیم کرده‌اید'], ['Android', 'android', 'ثابت، توسط SDK'], ['iOS', 'ios', 'ثابت، توسط SDK'], ['Node.js', 'nodejs', 'ثابت، توسط SDK'], ['Laravel', 'laravel', 'ثابت، توسط SDK'], ].map(([sdk, platform, source]) => ( ))}
{h}
{sdk} {platform} {source}
اگر spanی بدون تگ پلتفرم برسد (span سفارشی، SDK قدیمی و غیره)، در دسته‌ی **«unknown»** قرار می‌گیرد. ### جدا کردن محصولات گزینه‌ای در init برای تغییر پلتفرم وجود ندارد. اگر یک پروژه هم اپ وب مشتریان و هم پنل مدیریت شما را دریافت می‌کند، آن‌ها را با یک `environment` جداگانه (که در صفحه‌ی عملکرد فیلتر دارد) یا با تگ روی spanهایی که خودتان می‌فرستید از هم جدا کنید: ```typescript AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production-admin', }); await AbrovaTrace.getInstance()?.trackOperation('loadReport', loadReport, { tags: { app: 'web-admin' }, }); ``` ### بهترین روش‌ها - کندترین سرویس‌ها: https://abrova.ir/docs/trace/top-hosts Hostهای پرتکرار نشان می‌دهد اپ شما بیشتر با کدام hostهای خارجی ارتباط دارد: APIهای خودتان، سرویس‌های ثالث مثل درگاه پرداخت و سرویس پیامک، CDNها و endpointهای آنالیتیکس. هر host بر اساس حجم درخواست رتبه می‌گیرد و تأخیر p95 و تعداد خطایش را نشان می‌دهد، تا فوراً سرویس ثالثی را پیدا کنید که بی‌صدا تجربه‌ی کاربر را خراب می‌کند. فقط درخواست‌های HTTP خروجی اینجا دیده می‌شوند. کوئری‌های دیتابیس، عملیات سفارشی و spanهای cache اینجا نمایش داده نمی‌شوند. ### آنچه می‌بینید برای هر host: - **Host**: مثلاً `api.payments.example.com` یا `cdn.example.com` - **درخواست‌ها**: مجموع در بازه‌ی زمانی انتخاب‌شده (hostها بر اساس همین رتبه می‌گیرند) - تأخیر **میانگین** و **p95** بر حسب میلی‌ثانیه - **خطاها**: درخواست‌هایی که HTTP 400 یا بالاتر برگردانده‌اند برای بررسی دقیق فراخوانی‌های خروجی کند یا ناموفق، از کارت‌های **عملیات** و **درخواست‌های ناموفق** در صفحه‌ی **ابرووا تریس ← Performance** در کنسول ابرووا استفاده کنید. ### چه چیزی host حساب می‌شود همه‌ی SDKهای رسمی host را خودکار از URL درخواست استخراج می‌کنند. مثلاً درخواستی به `https://api.payments.example.com/v1/charges` زیر `api.payments.example.com` قرار می‌گیرد. تفاوت مسیرها در این سطح اهمیتی ندارد؛ برای آن [عملکرد URL](/docs/trace/url-performance) را ببینید. ### الگوهای رایجی که باید زیر نظر بگیرید ### یک نمونه‌ی واقعی سناریوی رایج: p95 کلی اپ شما یک‌شبه از 300ms به 1.1s می‌رسد. نمودار تأخیر این را تأیید می‌کند، اما کارت عملیات عادی به نظر می‌رسد؛ همه‌ی عملیات به یک اندازه کند شده‌اند. Hostهای پرتکرار فوراً مقصر را نشان می‌دهد: p95 مربوط به `api.thirdparty.com` از 200ms به 2.4s رسیده است. حادثه‌ی *آن‌ها* حالا حادثه‌ی *شما* است، اما دست‌کم می‌دانید مشکل از کد شما نیست و می‌توانید به‌جای دنبال کردن یک شبح، اثرش را کم کنید (cache، async، fallback). ### بهترین روش‌ها می‌تواند ۱۰۰ تا ۲۰۰ میلی‌ثانیه از اولین فراخوانی کم کند'], ['مراقب hostهای جدید باشید', 'hostی کاملاً جدید که نزدیک بالای فهرست ظاهر می‌شود معمولاً یا یک انتشار برنامه‌ریزی‌شده است یا خطر نشت داده؛ در هر دو حالت بررسی کنید'], ]} /> - عملکرد آدرس‌ها: https://abrova.ir/docs/trace/url-performance عملکرد URL جدولی از تأخیر هر URL است که درخواست‌ها را بر اساس **URL کامل HTTP** گروه‌بندی می‌کند (نه بر اساس نام منطقی عملیات). مکمل کارت **عملیات** ([تأخیر API](/docs/trace/api-latency)) است: عملیات بر اساس نام عملیات جمع می‌شوند (`GET /users/:id`)، اما URLها بر اساس چیزی که مرورگر کاربر واقعاً درخواست کرده (`/users/4710`). وقتی لازم دارید بدانید *کدام صفحه‌های مشخص* کندند، نه فقط کدام routeها، از این استفاده کنید. برای routeهای بک‌اند (مسیرهای پارامتری) از عملیات استفاده کنید. برای فراخوانی‌های سمت کلاینت در هر صفحه، یا وقتی اصلاً قالب route ندارید (مثل فایل‌های استاتیک و فراخوانی‌های ثالث)، از عملکرد URL استفاده کنید. ### داده‌ها شامل چه چیزی است هر ردیف این ستون‌ها را دارد:
{['ستون', 'توضیح'].map((h) => ( ))} {[ ['method', 'متد HTTP (GET، POST، PUT، PATCH، DELETE و …)'], ['url', 'URL کامل درخواست'], ['request_count', 'تعداد کل درخواست‌ها برای این URL در بازه‌ی زمانی انتخاب‌شده'], ['avg_duration', 'میانگین مدت (ms)؛ تقریبی، اما کنار p95 مفید است'], ['p50', 'میانه؛ تجربه‌ی یک کاربر معمولی'], ['p95', 'دنباله‌ی کند؛ ۵٪ کندترین درخواست‌ها. بیشتر دردسر کاربر همین‌جاست'], ['max_duration', 'کندترین درخواست در این بازه؛ برای پیدا کردن موارد پرت'], ['error_count', 'تعداد پاسخ‌هایی با وضعیت HTTP 400 یا بالاتر'], ].map(([col, desc]) => ( ))}
{h}
{col} {desc}
ردیف‌ها بر اساس **p95 نزولی** مرتب می‌شوند تا کندترین‌ها بالا بیایند. ### تفاوت URL و عملیات این تفاوت موقع خواندن جدول مهم است: - **عملیات** یک قالب منطقی route است؛ `GET /api/users/:id` در کارت عملیات فقط یک ردیف است، هر شناسه‌ی کاربری که صدا زده شده باشد. - **URL** همان رشته‌ای است که SDK دیده؛ `https://api.example.com/users/4710` و `/users/4711` اینجا دو ردیف جدا هستند. اگر بک‌اند شما URLهای با تنوع بالا دارد (هر شناسه‌ی کاربر یک ردیف جدا)، جدول عملکرد URL خیلی زود شلوغ می‌شود. این عمدی است: به شما می‌گوید *کدام کاربر، رکورد یا صفحه‌ی مشخصی* کند است. برای مانیتورینگ کلی در سطح route، کارت عملیات را ترجیح دهید. ### الگوهای رایجی که باید زیر نظر بگیرید ### بهترین روش‌ها - داشبورد خطا: https://abrova.ir/docs/trace/error-dashboard داشبورد خطاهای ابرووا تریس همان صفحه‌ی **ابرووا تریس ← مشکلات** در کنسول ابرووا است. این صفحه خطاهای گروه‌بندی‌شده از همه‌ی پلتفرم‌های پروژه‌ی شما را با فیلترهای قدرتمند، مدیریت وضعیت و جزئیات کامل هر خطا نشان می‌دهد.
{[['ردیابی لحظه‌ای', 'خطاها به محض رسیدن در قالب مشکل گروه‌بندی می‌شوند'], ['فیلتر قدرتمند', 'فیلتر بر اساس وضعیت، تکرار، بازه‌ی زمانی، پلتفرم، محیط، release و متن'], ['مدیریت وضعیت', 'خطاها را از کشف تا حل شدن دنبال کنید']].map(([t, d]) => (
{t}

{d}

))}
### اجزای داشبورد #### زبانه‌های وضعیت هر زبانه‌ی وضعیت نشان می‌دهد چند مشکل در آن است:
{[ { label: 'باز', value: '۱۲', color: 'text-red-600' }, { label: 'در حال بررسی', value: '۳', color: 'text-zinc-50' }, { label: 'حل‌شده', value: '۱۵', color: 'text-emerald-400' }, { label: 'همه', value: '۲۴۷', color: 'text-zinc-50' }, ].map((s) => (

{s.value}

{s.label}

))}
#### فیلترها - **وضعیت:** باز (پیش‌فرض)، در حال بررسی، حل‌شده، نادیده‌گرفته، بایگانی‌شده، همه - **تکرار:** ۱ تا ۱۰، ۱۱ تا ۵۰، ۵۱ تا ۱۰۰، و بیش از ۱۰۰ رویداد - **بازه‌ی زمانی:** ۱ ساعت، ۲۴ ساعت، ۷ روز، ۳۰ روز (پیش‌فرض) یا بازه‌ی دلخواه - **پلتفرم، محیط، Release** #### فهرست خطاها هر مشکل در فهرست، پلتفرم، پیام خطا، وضعیت، تعداد رویدادها و آخرین زمان دیده شدنش را نشان می‌دهد. بین نمای **فهرست** و **خط زمانی** جابه‌جا شوید. ### نمای جزئیات خطا روی هر خطا کلیک کنید تا جزئیات کاملش را ببینید: - **پیام خطا و stack trace**: stack trace کامل (اگر source map یا dSYM آپلود شده باشد، از حالت مبهم خارج می‌شود) - **خط زمانی breadcrumbها**: ترتیب رویدادهایی که به خطا رسیدند - **داده‌های اضافه**: داده‌های سفارشی که با پارامتر extra ضمیمه شده‌اند - **اطلاعات محیط**: پلتفرم، سیستم‌عامل، مرورگر و نسخه‌ی release - **آمار رخدادها**: اولین و آخرین زمان دیده شدن، تعداد کل و تاریخچه‌ی وضعیت ### مدیریت وضعیت
{[ ['باز', 'خطای جدیدی که باید بررسی شود', 'bg-blue-900/20 text-blue-300'], ['در حال بررسی', 'در حال بررسی یا رفع است', 'bg-yellow-900/20 text-yellow-300'], ['حل‌شده', 'رفع آن منتشر شده است', 'bg-green-900/20 text-green-300'], ['بایگانی‌شده', 'بسته شده و دیگر فعال نیست', 'bg-zinc-800 bg-zinc-700 text-zinc-400'], ['نادیده‌گرفته', 'مشکل شناخته‌شده‌ای که رفع نمی‌شود', 'bg-zinc-800 bg-zinc-700 text-zinc-400'], ].map(([status, desc, cls]) => (
{status} {desc}
))}
**تغییر گروهی:** چند مشکل را انتخاب کنید تا یک‌جا آن‌ها را حل‌شده، در حال بررسی، نادیده‌گرفته یا بایگانی‌شده کنید یا دوباره باز کنید، یا از منوی **⋯** همه‌ی مشکل‌های باز را حل‌شده کنید. تغییر وضعیت دست‌کم نقش member می‌خواهد؛ حذف مشکل‌ها نقش admin یا owner. جزئیات در [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles).
### نقش‌ها و دسترسی‌ها کاری که در ابرووا تریس می‌توانید انجام دهید به نقش شما بستگی دارد. نقش‌ها در کنسول ابرووا (تنظیمات سازمان و پروژه) داده می‌شوند؛ ابرووا تریس فهرست نقش جداگانه‌ای ندارد. هر نقش همه‌ی اختیارات نقش بالاتر از خود در جدول را هم دارد. - **فقط پروژه‌های خودتان را می‌بینید.** memberها و viewerها پروژه‌هایی را می‌بینند که به آن‌ها اختصاص داده شده‌اند؛ پروژه‌های دیگر سازمان برایشان در ابرووا تریس نمایش داده نمی‌شوند. - **فقط به یک پروژه اضافه شده‌اید؟** کسی که بدون نقش در سازمان فقط به یک پروژه اضافه شده، آنجا مثل viewer فقط دسترسی خواندن دارد. - **کارهای غیرمجاز.** کنترل‌هایی که نمی‌توانید استفاده کنید غیرفعال نشان داده می‌شوند. اگر درخواستی نقش بالاتری بخواهد، API پاسخ `403` با `"code": "forbidden_role"` برمی‌گرداند. - **اپ‌ها و سرورها جدا هستند.** نقش‌ها برای افرادی است که از داشبورد استفاده می‌کنند. SDKها، log agent و check-inهای cron با کلیدهای پروژه احراز هویت می‌شوند؛ [کلید سرور و routeهای سرور](/docs/trace/server-secret) را ببینید. ### سهمیه‌ی رایگان و کیف پول ابرووا تریس **رویدادهای خطا در هر ماه** را برای سازمان شما می‌شمارد: هر خطایی که می‌رسد، از جمله تکرارِ یک مشکلِ شناخته‌شده. روی صفحه، رویدادهای یک علت در یک مشکل گروه‌بندی می‌شوند تا فهرست خوانا بماند. هر ماه **۵۰٬۰۰۰ رویداد خطا رایگان** است. بیشتر از آن، هر **۱٬۰۰۰ رویدادِ شروع‌شده ۳٬۰۰۰ تومان** هزینه دارد که روزی یک بار از کیف پول پیش‌پرداخت سازمان کم می‌شود؛ [قیمت‌ها](/pricing) را ببینید. اشتراکی در کار نیست. در طرح رایگان، تا **۱٬۰۰۰ مشکل جدید** در ماه هم ذخیره می‌شود. وقتی سهم رایگان تمام شده و کیف پول موجودی ندارد (پس از صفر شدن موجودی، پرداخت به اندازه‌ی مصرف ۳ روز دیگر فعال می‌ماند): - **رویدادهای جدید ذخیره نمی‌شوند** تا وقتی سهم رایگان در شروع ماه بعد تازه شود، یا کیف پول را شارژ کنید. - **چیزی از داده‌های قبلی پاک نمی‌شود.** - **اپ‌های شما هیچ‌وقت مختل نمی‌شوند.** API همچنان پاسخ موفق (`200`) می‌دهد، با بدنه‌ای که می‌گوید رویداد ذخیره نشده، پس هیچ SDKی دوباره تلاش نمی‌کند، صف نمی‌کند یا خطا لاگ نمی‌کند: ```json { "status": "dropped", "code": "plan_limit_reached", "metric": "error_events_monthly", "limit": 50000, "used": 50000, "resets_at": "2026-11-01T00:00:00.000Z" } ``` (`limit` و `used` مقدارهای نمونه‌اند؛ سهم رایگان و مصرف این ماهِ سازمان شما را نشان می‌دهند.) پایش عملکرد (spanها و Web Vitals) بخشی از پرداخت به اندازه‌ی مصرف است و جزو رویدادهای خطا شمرده نمی‌شود. ### جست‌وجو و فیلتر - **جست‌وجوی متنی**: جست‌وجو در پیام‌های خطا، پلتفرم و محیط - **فیلترهای ترکیبی**: ترکیب وضعیت، تکرار، بازه‌ی زمانی، پلتفرم، محیط، release و جست‌وجوی متنی - **صفحه‌بندی**: پیمایش راحت در فهرست‌های بزرگ خطا ### بهترین روش‌ها - استک‌تریس و source map: https://abrova.ir/docs/trace/stack-traces stack trace ترتیب دقیق فراخوانی توابعی را که به خطا رسیده‌اند نشان می‌دهد: **کجا** خطا رخ داد، کد **چطور** به آنجا رسید و **چه** توابعی صدا زده شدند. ### ثبت خودکار stack trace پیکربندی اضافه‌ای لازم نیست. ابرووا تریس stack trace را خودکار از این‌ها ثبت می‌کند: - exceptionهای مدیریت‌نشده (با `captureUncaughtErrors: true`) - ثبت دستی exception (با `captureException()`) - خطاهای async (با `initWithZone()`) - exceptionهای پلتفرم از کد نیتیو ```dart try { await riskyOperation(); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, // Always include this! message: 'Operation failed', extra: {'user_id': 'user_123'}, ); } ``` ### خواندن stack trace

Exception: FormatException: Invalid input

{" "}at Parser.parse (lib/parser.dart:42:15) {'←'} محل رخ دادن خطا

{" "}at DataService.process (lib/data_service.dart:108:8)

{" "}at HomeScreen.loadData (lib/screens/home.dart:55:12)

{" "}at _HomeScreenState.initState (lib/screens/home.dart:23:5)

- **خط اول:** جایی که خطا رخ داده است - **خطوط بعدی:** call stack (کد چطور به آنجا رسیده) - **مسیر فایل‌ها و شماره‌ی خطوط:** برای پیدا کردن جای دقیق مشکل ### stack trace در هر پلتفرم
{[ ['Flutter موبایل', 'stackهای کامل Dart با شماره‌ی خط و نام متدها'], ['Flutter وب', 'stackهای خطای جاوااسکریپت همراه با اطلاعات مرورگر؛ برای خوانا شدن ممکن است source map لازم باشد'], ['Flutter دسکتاپ', 'اطلاعات کامل debug با stackهای کامل و جزئیات پلتفرم'], ['افزونه‌ی Chrome', 'اطلاعات context افزونه همراه با نسخه‌ی Chrome و context پس‌زمینه یا popup'], ].map(([platform, desc]) => (
{platform}

{desc}

))}
### افزودن context به stack trace ```dart try { await processPayment(orderId, amount); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Payment processing failed', extra: { 'order_id': orderId, 'amount': amount, 'payment_method': paymentMethod, }, ); } ``` ### stack trace کرش‌های نیتیو کرش‌های نیتیو در اجرای بعدی اپ شناسایی می‌شوند. وقتی کرش اپ را می‌بندد، اپ نمی‌تواند همان لحظه گزارش بدهد؛ برای همین ابرووا تریس کرش را هنگام اجرای دوباره پیدا می‌کند. ثبت کرش نیتیو هنگام مقداردهی اولیه‌ی SDK (AbrovaTrace.initWithZone() یا AbrovaTrace.init()) نصب می‌شود و فراخوانی اضافه‌ای لازم نیست. AbrovaTrace.close() فقط داده‌های در انتظار را می‌فرستد و SDK را می‌بندد. ### آپلود source map برای پلتفرم‌های وب (Next.js، Flutter Web) فایل‌های `.map` را آپلود کنید تا stack traceهای production به فایل‌ها و شماره‌ی خطوط اصلی کد شما برگردند. #### کجا آپلود کنیم - **کنسول ابرووا**: در `console.abrova.ir` → پروژه‌ی شما → **ابرووا تریس → Source map و symbol**. نسخه‌ی release را تنظیم کنید و فایل‌های `.map` را (چند فایل با هم) آپلود کنید. - **Abrova CLI**: از build یا CI با `abrova-trace sourcemaps upload` آپلود کنید. [آپلود source map](/docs/trace/upload-source-maps) را ببینید. - [اپ دسکتاپ](/desktop) ابرووا هم وقتی منتشر شود همین امکان آپلود را خواهد داشت (به‌زودی). نسخه‌ی release باید با فیلد `release` که هنگام init به SDK می‌دهید یکی باشد؛ بک‌اند از همین راه map درست را به هر خطا وصل می‌کند. آپلود دست‌کم نقش member می‌خواهد؛ حذف فایل‌های آپلودشده نقش admin یا owner. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. #### Next.js ```javascript // next.config.js module.exports = { productionBrowserSourceMaps: true, }; ``` بعد از `next build`، فایل‌های `.map` را کنار chunkها در `.next/static/chunks/` پیدا می‌کنید. در هر آپلود تا ۵۰۰ فایل `.map` پذیرفته می‌شود، پس می‌توانید همه را یک‌جا بیندازید (فایل‌هایی که `.map` نیستند نادیده گرفته می‌شوند). #### Flutter Web با source map فعال build بگیرید، سپس تنها فایل `main.dart.js.map` را آپلود کنید: ```bash flutter build web --source-maps ``` فایل map در `build/web/main.dart.js.map` قرار می‌گیرد.

dart2js (کامپایلری که Dart را به جاوااسکریپت تبدیل می‌کند) در buildهای --release به‌شدت inline می‌کند. بعضی frameها تمیز به فایل‌های .dart شما برمی‌گردند. بعضی دیگر به بخش‌های داخلی فریم‌ورک (js_helper.dart، dart:async) می‌رسند یا minified می‌مانند (a3v.$0)، چون فایل .map برای آن بایت‌های JS اصلاً نگاشتی ندارد.

این ویژگی خروجی dart2js است، نه مشکل resolver. از اول می‌گوییم چون بیشتر تیم‌ها در production به آن برمی‌خورند و فکر می‌کنند تنظیماتشان خراب است. خراب نیست.

جایی که ابرووا جلو می‌افتد context است. breadcrumbهای gesture را فعال کنید (AbrovaTrace.enableGestureBreadcrumbs()) تا هر مشکل ویجتی را که کاربر درست پیش از خطا لمس کرده نشان دهد، به‌همراه route، آخرین فراخوانی HTTP و هر breadcrumb سفارشی. همراه با trace نیمه‌بازگردانده‌شده، گزارش باگی قابل‌بازتولید خواهید داشت.

فقط برای QA / staging می‌توانید بهینه‌سازی کمتری را اجبار کنید تا traceهای کاملاً بازگردانده‌شده بگیرید:

حجم bundle حدود ۳۰٪ بیشتر می‌شود؛ برای staging مناسب است، برای production توصیه نمی‌شود.

### بهترین روش‌ها - آپلود source map (CLI): https://abrova.ir/docs/trace/upload-source-maps ابزار `@abrova-trace/cli` فایل‌های source map را از pipeline مربوط به CI/CD به ابرووا تریس آپلود می‌کند. هر خطایی که **بعد از** آپلود ثبت شود، در داشبورد با نام واقعی فایل‌ها و شماره‌ی خطوط نمایش داده می‌شود؛ دیگر خبری از stackهای minified مثل `4d240bf7.js:1:16945` نیست.

هر بار که build مربوط به production را با minification منتشر می‌کنید (Next.js، Vite، webpack، Rollup). بدون source mapهای آپلودشده، هر خطا در داشبورد ابرووا تریس stack فشرده‌شده را نشان می‌دهد که برای debug به کاری نمی‌آید.

پیش‌نیازها

سه چیز لازم دارید: - **Node.js 18+** روی ماشینی که آپلود را اجرا می‌کند (runner مربوط به CI یا سیستم توسعه‌ی خودتان) - **یک توکن دسترسی شخصی** با دسترسی `sourcemaps:write` - **DSN پروژه** از بخش **ابرووا تریس ← Source map و symbol** در کنسول ابرووا

نصب CLI

این CLI از Abrova Center می‌آید. یک بار رجیستری را اضافه کنید، سپس آن را به‌عنوان dev dependency نصب کنید: ```bash # .npmrc (in your project root) @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` ```bash npm install --save-dev @abrova-trace/cli ``` آن را از داخل پروژه با `npx abrova-trace` اجرا کنید (مثلاً `npx abrova-trace --help`). نام دستور `abrova-trace` است.

ساختن توکن دسترسی شخصی

توکن‌های دسترسی شخصی (PAT) اعتبارنامه‌هایی بلندمدت و وابسته به کاربرند؛ همان الگویی که بسیاری از CLIهای توسعه‌دهندگان به کار می‌برند. در جریان‌های خودکار، جای توکن نشست کاربر را می‌گیرند. 1. کنسول ابرووا را باز کنید: **[Account → Personal Access Tokens](https://console.abrova.ir/dashboard/account/tokens)** 2. روی **token جدید** کلیک کنید 3. یک نام به آن بدهید (مثلاً `CI upload — production`) 4. دسترسی **`sourcemaps:write`** را فعال کنید 5. تاریخ انقضا را انتخاب کنید (برای CI بدون انقضا هم مشکلی ندارد؛ ۹۰ روز یک حد وسط رایج است) 6. روی **ساخت token** کلیک کنید 7. **مقدار `ab_pat_xxx` را همان لحظه کپی کنید**؛ فقط یک بار نمایش داده می‌شود این مقدار را در محیط CI به‌عنوان secret با نام `ABROVA_TRACE_TOKEN` ذخیره کنید.
  • توکن‌ها در سمت ما به‌صورت hash ذخیره می‌شوند؛ توکن گم‌شده را نمی‌توانیم بازیابی کنیم، فقط می‌شود آن را باطل کرد و توکن تازه ساخت.
  • خود توکن اعتبارنامه است؛ هر کسی آن را داشته باشد می‌تواند در هر کدام از پروژه‌های شما source map آپلود کند.
  • اگر لو رفت، از همان صفحه فوراً باطلش کنید. همه‌ی کارهای CLI/CI که از آن استفاده می‌کنند همان لحظه از کار می‌افتند.
  • هرگز توکن را در git commit نکنید. از secretهای سرویس CI خود استفاده کنید (GitHub Actions Secrets، متغیرهای GitLab CI و غیره).

پیدا کردن شناسه‌ی پروژه

«project ID» در این CLI همان **DSN پروژه** برای پلتفرم وبی است که mapها به آن تعلق دارند (در یک پروژه‌ی ابرووا، هر پلتفرم DSN جداگانه‌ی خودش را دارد). 1. کنسول ابرووا را باز کنید و پروژه‌تان را انتخاب کنید 2. به **ابرووا تریس ← Source map و symbol** بروید (زبانه‌ی **Source maps**) 3. در کارت **آپلود از CI**، اگر پروژه بیش از یک پلتفرم دارد پلتفرم وب را انتخاب کنید، سپس **DSN پروژه برای CLI** را کپی کنید این مقدار را با نام `ABROVA_TRACE_PROJECT` در محیط CI ذخیره کنید (CLI متغیر قدیمی `ABROVA_TRACE_DSN` را هم همچنان می‌خواند).

آپلود از CI

این دو خط را **درست بعد از build مربوط به production** که فایل‌های source map را می‌سازد، به pipeline اضافه کنید: ```bash export ABROVA_TRACE_TOKEN=ab_pat_xxx export ABROVA_TRACE_PROJECT= npx abrova-trace sourcemaps upload ./path/to/your/sourcemaps \ --release "1.2.3" ``` همین. CLI همه‌ی فایل‌های `.map` را در پوشه‌ی داده‌شده و زیرپوشه‌هایش پیدا می‌کند، همه را در یک درخواست آپلود می‌کند و با نسخه‌ی release برچسب می‌زند تا ابرووا تریس بتواند آن‌ها را با خطاهای همان نسخه جور کند.

مثال: آپلود source mapهای Next.js

در Next.js ساخت source map را در `next.config.js` فعال کنید: ```js module.exports = { productionBrowserSourceMaps: true, }; ``` سپس در اسکریپت CI: ```bash #!/bin/bash set -e # 1. Build with sourcemaps npm run build # 2. Copy sourcemaps into a dedicated folder mkdir -p ./sourcemaps find .next/static/chunks -name "*.js.map" -exec cp {} ./sourcemaps \; # 3. Upload to Abrova Trace tagged with the release version npx abrova-trace sourcemaps upload \ --release "$NEXT_PUBLIC_APP_VERSION" \ ./sourcemaps # 4. Remove sourcemaps from the deployed build so browsers can't download them # (source stays private — Abrova keeps the maps server-side) find .next/static/chunks -name "*.js.map" -delete find .next/static/chunks -name "*.js" -exec \ sed -i '/^\/\/# sourceMappingURL=/d' {} + ```

یکسان بودن نسخه‌ی release

مقدار فلگ `--release` باید با نسخه‌ای که SDK هنگام ارسال خطا می‌فرستد یکی باشد. اگر SDK مربوط به Next.js این‌طور مقداردهی شده است: ```typescript AbrovaTrace.init({ apiKey: process.env.NEXT_PUBLIC_ABROVA_TRACE_API_KEY!, release: process.env.NEXT_PUBLIC_APP_VERSION, // e.g. "1.2.3" }); ``` با این دستور آپلود کنید: ```bash npx abrova-trace sourcemaps upload \ --release "$NEXT_PUBLIC_APP_VERSION" \ ./sourcemaps ``` اگر رشته‌های release یکی نباشند، ابرووا تریس source map را برای خطا پیدا نمی‌کند و همان stack فشرده‌شده را نشان می‌دهد.

«source mapها را آپلود کردم اما خطاها هنوز stack فشرده‌شده نشان می‌دهند.»

معمولاً رشته‌ی release در آپلود با چیزی که SDK می‌فرستد یکی نیست. نسخه‌ی release را در صفحه‌ی جزئیات خطا در داشبورد ابرووا تریس ببینید، سپس دقیقاً با همان رشته دوباره آپلود کنید.

گزینه‌های CLI

', 'DSN پروژه (یا متغیر محیطی ABROVA_TRACE_PROJECT را تنظیم کنید). --dsn نام دیگر همین گزینه است'], ['--release ', 'الزامی. نسخه‌ی releaseی که این bundle به آن تعلق دارد'], ['--token ', 'توکن دسترسی شخصی (یا متغیر محیطی ABROVA_TRACE_TOKEN را تنظیم کنید)'], ['--api-url ', 'آدرس پایه‌ی Trace API (یا متغیر محیطی ABROVA_TRACE_API_URL را تنظیم کنید). پیش‌فرض: https://trace.abrova.ir'], ['--dry-run', 'پیش‌نمایش فایل‌هایی که آپلود می‌شوند، بدون ارسال آن‌ها'], ]} /> فلگ بر متغیر محیطی متناظرش اولویت دارد. کلید API پروژه (`ab_live_…`) اینجا کار نمی‌کند؛ CLI توکن دسترسی شخصی لازم دارد.

ابرووا تریس روی سرور خودتان

نسخه‌ی self-hosted ابرووا تریس به‌زودی می‌آید. [Self-Hosted](/docs/trace/self-hosted) را ببینید. وقتی ابرووا تریس را روی زیرساخت خودتان اجرا می‌کنید، CLI را به API خودتان وصل کنید: ```bash export ABROVA_TRACE_API_URL=https://trace.mycompany.com npx abrova-trace sourcemaps upload \ --release "1.2.3" \ ./sourcemaps ``` CLI روی نسخه‌ی self-hosted دقیقاً همان کار را می‌کند؛ بدون تغییر کد، فقط با تغییر URL.

کدهای خروج

- **`0`**: موفق - **`1`**: هر خطایی (توکن نامعتبر، خطای شبکه، رد شدن آپلود و غیره) مناسب برای این‌که deploy فقط بعد از آپلود موفق انجام شود: ```yaml # .github/workflows/deploy.yml - name: Upload sourcemaps run: npx abrova-trace sourcemaps upload --release "$GITHUB_SHA" ./sourcemaps env: ABROVA_TRACE_TOKEN: ${{ secrets.ABROVA_TRACE_TOKEN }} ABROVA_TRACE_PROJECT: ${{ secrets.ABROVA_TRACE_PROJECT }} - name: Deploy run: ./deploy.sh # Deploy only runs if sourcemap upload succeeded ```

محدودیت‌های آپلود

- **بیشینه‌ی حجم bundle:** ۱۱۰ مگابایت در هر آپلود (کل درخواست multipart) - **بیشینه‌ی حجم فایل:** ۵۰ مگابایت برای هر فایل `.map` - **بیشینه‌ی تعداد فایل در هر آپلود:** ۵۰۰ این مقادیر پیش‌فرض سخاوتمندانه‌اند و برای buildهای معمول Next.js، Vite و webpack کافی‌اند. فایل‌های بزرگ‌تر با HTTP 413 رد می‌شوند. اگر bundle شما از ۱۱۰ مگابایت بیشتر است، آن را در چند آپلود با همان `--release` تقسیم کنید (مثلاً هر بار یک پوشه)، یا [با ما تماس بگیرید](/contact).

dSYMهای iOS

CLI فقط source mapهای جاوااسکریپت را آپلود می‌کند. debug symbolهای iOS (dSYM) در کنسول ابرووا آپلود می‌شوند: 1. در Xcode به **Organizer › Archives › Download Debug Symbols** بروید، سپس پوشه‌ی `.dSYM` را zip کنید 2. **ابرووا تریس ← Source map و symbol** را باز کنید و زبانه‌ی **dSYMهای iOS** را انتخاب کنید 3. release را انتخاب کنید و فایل `.zip` را رها کنید (در هر آپلود یک `.zip`، تا ۱۰ مگابایت) آپلود دست‌کم نقش member می‌خواهد؛ حذف فایل‌های آپلودشده نقش admin یا owner. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید.

عیب‌یابی

- گروه‌بندی خطا: https://abrova.ir/docs/trace/error-grouping گروه‌بندی خطاها خطاهای یکسان را خودکار در یک گروه جمع می‌کند؛ به‌جای دیدن ۱٬۰۰۰ خطای جدا برای یک باگ، یک خطا با شمارش ۱٬۰۰۰ می‌بینید. این کار نویز را کم می‌کند و کمک می‌کند روی مشکل‌های متمایز تمرکز کنید.
{[['نویز کمتر', 'خطاهای یکسان با هم گروه‌بندی می‌شوند'], ['ردیابی تکرار', 'تعداد رخدادها و روندشان را ببینید'], ['خودکار', 'پیکربندی لازم ندارد']].map(([t, d]) => (
{t}

{d}

))}
### خطاها چطور گروه‌بندی می‌شوند خطاهایی که پیام، stack trace و پلتفرم یکسان دارند در یک مشکل گروه‌بندی می‌شوند. هر تفاوتی در متن پیام یا stack trace یک مشکل جدید می‌سازد. ### چه زمانی خطاها با هم گروه می‌شوند
با هم گروه می‌شوند
  • خطای یکسان در نشست‌های مختلف کاربران
  • خطای تکراری از یک کاربر
  • خطای یکسان در محیط‌ها و releaseهای مختلف (اگر پلتفرم یکی باشد)
با هم گروه نمی‌شوند
  • پیام‌های خطای متفاوت
  • stack traceهای متفاوت (حتی با تغییر یک شماره‌ی خط)
  • پلتفرم‌های متفاوت (flutter_web در برابر flutter_android)
### آشنایی با گروه‌های خطا هر گروه این‌ها را نگه می‌دارد:
{[ { label: 'تعداد رخداد', value: '۱٬۲۴۷' }, { label: 'اولین بار دیده‌شده', value: '2025-11-10' }, { label: 'آخرین بار دیده‌شده', value: '2025-11-12' }, { label: 'وضعیت', value: 'باز' }, ].map((item) => (

{item.value}

{item.label}

))}
### دسته‌های تکرار هر گروه بر اساس تعداد رویدادهایش در یک دسته‌ی تکرار قرار می‌گیرد (فیلتر **تکرار** در صفحه‌ی مشکل‌ها؛ در API به آن `severity` می‌گویند):
{['تکرار', 'رویدادها', 'رنگ'].map((h) => ( ))} {[ ['بسیار پرتکرار', 'بیش از ۱۰۰', 'bg-red-900/30 text-red-400'], ['پرتکرار', '۵۱ تا ۱۰۰', 'bg-orange-900/30 text-orange-300'], ['تکرارشونده', '۱۱ تا ۵۰', 'bg-yellow-900/30 text-yellow-300'], ['نادر', '۱ تا ۱۰', 'bg-blue-900/30 text-blue-300'], ].map(([level, count, cls]) => ( ))}
{h}
{level} {count}
### مدیریت گروه‌های خطا - **مدیریت وضعیت:** باز، در حال بررسی، حل‌شده، بایگانی‌شده، نادیده‌گرفته - **عملیات گروهی:** چند گروه را انتخاب کنید تا وضعیتشان را تغییر دهید یا حذفشان کنید - **فیلتر:** گروه‌ها را بر اساس وضعیت، تکرار، بازه‌ی زمانی، پلتفرم، محیط، release یا متن جست‌وجو فیلتر کنید ### بهترین روش‌ها - Breadcrumbs: https://abrova.ir/docs/trace/breadcrumbs breadcrumbها ردی از رویدادهایی هستند که پیش از رخ دادن خطا اتفاق افتاده‌اند. کمک می‌کنند بفهمید کاربر وقتی مشکل پیش آمد مشغول چه کاری بود.

نمونه‌ی مسیر کاربر:

{['کاربر اپ را باز کرد', '← به /checkout رفت', '← روی «پرداخت» کلیک کرد', '← \u{1F4A5} پرداخت ناموفق بود'].map((step, i) => ( {step} ))}
### Flutter #### ثبت خودکار ناوبری ```dart MaterialApp( navigatorObservers: [ AbrovaTraceNavigatorObserver(), // Tracks all screen changes ], home: HomePage(), ) ``` #### ثبت خودکار لمس‌ها با یک فراخوانی، برای هر لمس روی دکمه، سوییچ، ردیف فهرست یا هر ویجت تعاملی breadcrumb ثبت می‌شود؛ در Web، Android، iOS، macOS، Windows و Linux. هر breadcrumb نوع ویجت، برچسب، `ValueKey` (اختیاری)، route فعلی و مختصات لمس را ثبت می‌کند. ```dart // Call once after AbrovaTrace.init() and WidgetsFlutterBinding.ensureInitialized() AbrovaTrace.enableGestureBreadcrumbs(); ``` برای فهرست کامل ویجت‌های شناخته‌شده، ترتیب استخراج برچسب و نکات حریم خصوصی، صفحه‌ی SDK فلاتر را ببینید. ### افزودن breadcrumb دستی #### Flutter ```dart // User action AbrovaTrace.addBreadcrumb( 'User clicked checkout button', type: BreadcrumbType.user, data: {'cart_items': 3, 'total': 49.99}, ); // HTTP request AbrovaTrace.addBreadcrumb( 'POST /api/orders', type: BreadcrumbType.http, data: {'status_code': 200, 'duration_ms': 150}, ); // Custom event AbrovaTrace.addBreadcrumb( 'Cache miss for user profile', type: BreadcrumbType.info, data: {'cache_key': 'user_123'}, ); ``` #### Node.js ```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); const { Breadcrumb } = AbrovaTrace; AbrovaTrace.addBreadcrumb( Breadcrumb.http('POST', '/api/login', 200, 150) ); AbrovaTrace.addBreadcrumb( Breadcrumb.database('SELECT * FROM users WHERE id = ?', 25) ); AbrovaTrace.addBreadcrumb( Breadcrumb.user('clicked_checkout_button', { cart_total: 99.99 }) ); ``` #### Next.js ```tsx import { useBreadcrumbs } from '@abrova-trace/nextjs-sdk/react'; const { addBreadcrumb, addHttp } = useBreadcrumbs(); addBreadcrumb('Form submitted', { formName: 'contact' }); addHttp('GET', '/api/users', 200, { duration_ms: 45 }); ``` ### انواع breadcrumb
{['نوع', 'توضیح'].map((h) => ( ))} {[ ['BreadcrumbType.user', 'کارهای کاربر (کلیک، لمس، کشیدن)'], ['BreadcrumbType.navigation', 'تغییر صفحه (با observer خودکار)'], ['BreadcrumbType.http', 'فراخوانی‌های API و درخواست‌های شبکه'], ['BreadcrumbType.state', 'تغییرات وضعیت اپ'], ['BreadcrumbType.info', 'رویدادهای اطلاعاتی عمومی'], ['BreadcrumbType.error', 'رویدادهای خطا'], ['BreadcrumbType.system', 'رویدادهای SDK و سیستم'], ].map(([type, desc]) => ( ))}
{h}
{type} {desc}
### مثال کامل ```dart class CheckoutPage extends StatelessWidget { Future _checkout() async { // Track user action AbrovaTrace.addBreadcrumb( 'User initiated checkout', type: BreadcrumbType.user, data: {'cart_items': 3}, ); try { // Track HTTP call AbrovaTrace.addBreadcrumb( 'POST /api/orders', type: BreadcrumbType.http, ); final order = await api.createOrder(); AbrovaTrace.addBreadcrumb( 'Order created successfully', type: BreadcrumbType.info, data: {'order_id': order.id}, ); } catch (e, stackTrace) { // Error will include ALL breadcrumbs above await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Checkout failed', ); } } @override Widget build(BuildContext context) { return ElevatedButton( onPressed: _checkout, child: Text('Place Order'), ); } } ``` ### دیدن breadcrumbها breadcrumbها هنگام دیدن جزئیات هر خطا خودکار در داشبورد نمایش داده می‌شوند. یک خط زمانی نشان می‌دهند: رویدادهای ناوبری، کلیک‌های کاربر، درخواست‌های API و در آخر خود خطا. ### بهترین روش‌ها - معرفی لاگ‌ها: https://abrova.ir/docs/trace/logs لاگ‌های کانتینرهای Docker، SDKها و ارسال مستقیم از API را یک‌جا جمع و تحلیل کنید. لاگ‌ها را به‌صورت بلادرنگ ببینید، جستجو و فیلتر کنید. ### شروع سریع: ایجنت برای Docker ```bash docker run -d \ --name abrova-trace-agent \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -e ABROVA_TRACE_API_KEY=ab_srv_your_server_secret \ ghcr.io/abrova-co/abrova-trace-agent:latest ``` از کلید سرور پروژه (`ab_srv_...`) در کنسول ابرووا استفاده کنید. لاگ همه‌ی کانتینرها در **ابرووا تریس › Logs** نمایش داده می‌شود. راه‌اندازی روی چند host، مدیریت کانتینرها و همه‌ی گزینه‌ها: [ایجنت ابرووا تریس](/docs/trace/agent). ### شروع سریع: لاگ‌گیری با SDK با SDKهای native، لاگ‌ها به‌طور خودکار دسته‌ای ارسال می‌شوند، تلاش دوباره با فاصله‌ی افزایشی (exponential backoff) انجام می‌شود و چرخه‌ی حیات اپ موبایل در نظر گرفته می‌شود. #### Flutter ```dart // Enable logging AbrovaTrace.enableLogging(sourceId: 'my-flutter-app'); // Send logs AbrovaTrace.info('User login successful', metadata: {'user_id': 'user_123'}); AbrovaTrace.warn('Slow API response', metadata: {'duration_ms': 2500}); AbrovaTrace.logError('Database query failed', metadata: {'query': 'SELECT ...'}); ``` #### Node.js ```javascript const { LogService, LogLevel } = require('@abrova-trace/nodejs-sdk'); const logger = new LogService({ apiKey: 'ab_live_your_api_key', sourceId: 'api-server', environment: 'production', }); logger.log('Server started', LogLevel.INFO); logger.log('Payment failed', LogLevel.ERROR, { orderId: '12345' }); ``` #### Next.js ```typescript import { LogService } from '@abrova-trace/nextjs-sdk'; const logger = new LogService({ apiKey: process.env.ABROVA_TRACE_API_KEY!, sourceId: 'nextjs-app', platform: 'nextjs', environment: process.env.NODE_ENV, }); logger.log('Request received', 'info', { path: '/api/users' }); ``` **قابلیت‌های SDK:** تلاش دوباره با فاصله‌ی افزایشی (حداکثر ۶۰ ثانیه بین تلاش‌ها)، بافر با سقف ۱٬۰۰۰ لاگ، تایمرهای تنبل (در حالت بیکار باتری مصرف نمی‌شود)، و hookهای چرخه‌ی حیات موبایل (ارسال خودکار هنگام رفتن اپ به پس‌زمینه). جزئیات در هر SDK فرق دارد؛ صفحه‌ی لاگ‌گیری هر SDK را ببینید. ### شروع سریع: ارسال از API فهرست کامل فیلدها و نمونه‌ها به زبان‌های دیگر: [REST API برای لاگ‌ها](/docs/trace/logs-rest-api). ```bash curl -X POST https://trace.abrova.ir/api/logs/ingest \ -H "x-api-key: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "sourceId": "api-server", "level": "info", "message": "Server started on port 3000" }' ``` ### ارسال دسته‌ای ```bash curl -X POST https://trace.abrova.ir/api/logs/ingest/batch \ -H "x-api-key: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "sourceId": "api-server", "logs": [ { "level": "info", "message": "Request received" }, { "level": "debug", "message": "Processing..." }, { "level": "info", "message": "Response sent in 45ms" } ] }' ``` ### سطح‌های لاگ - **fatal** — خطاهای بحرانی و غیرقابل بازیابی - **error** — خطاهایی که باید بررسی شوند - **warn** — هشدار درباره‌ی مشکلات احتمالی - **info** — اطلاعات عمومی اپلیکیشن - **debug** — اطلاعات دقیق برای دیباگ - **trace** — دیباگ بسیار جزئی در سطح trace ### سقف‌ها و هزینه‌ها رایگان در هر ماه: ۱ گیگابایت لاگ. بیش از آن، هزینه از کیف پول: هر گیگابایت ۱۵٬۰۰۰ تومان. لاگ‌ها ۳۰ روز نگه داشته می‌شوند. [قیمت‌ها](/pricing) را ببینید. ارسال لاگ برای هر کلید و هر آدرس کلاینت سقف درخواست بالایی دارد. کلاینتی که از این سقف بگذرد پاسخ HTTP `429` با هدر `Retry-After` می‌گیرد؛ با ارسال دسته‌ای لاگ‌ها بسیار پایین‌تر از این سقف می‌مانید. - ایجنت ابرووا تریس (Docker): https://abrova.ir/docs/trace/agent ایجنت ابرووا تریس لاگ همه‌ی کانتینرهای Docker روی سرور شما را استریم می‌کند؛ همراه با مدیریت لحظه‌ای کانتینرها، پشتیبانی از چند سرور و تشخیص خودکار سطح لاگ. برای راه‌اندازی فقط یک دستور `docker run` لازم است.
  • لاگ زنده‌ی Docker در کنسول ابرووا — همه‌ی کانتینرها و همه‌ی سرورها در یک فید
  • مدیریت کانتینر از کنسول: start، stop، restart و حذف
  • آمار زنده‌ی CPU، حافظه و شبکه برای هر کانتینر
  • چند سرور: یک collector لاگ ایجنت‌های راه دور روی سرورهای دیگر را جمع می‌کند
  • تشخیص خودکار سطح لاگ، یکی کردن خروجی چندخطی (هر stack trace یک ورودی می‌شود) و پوشاندن اطلاعات محرمانه پیش از خروج هر چیزی از سرور
لاگ کانتینرها جزو لاگ‌های ابرووا تریس شما حساب می‌شود: ۱ گیگابایت رایگان در هر ماه، و بعد از آن هر گیگابایت ۱۵٬۰۰۰ تومان هزینه از کیف پول. [قیمت‌ها](/pricing) را ببینید. ### نحوه‌ی کار ایجنت از معماری **collector + ایجنت راه دور** استفاده می‌کند: 1. **collector** روی سرور اصلی شما اجرا می‌شود. کانتینرهای محلی را زیر نظر می‌گیرد، به ایجنت‌های راه دور وصل می‌شود و لاگ‌ها را دسته‌دسته از طریق HTTPS به ابرووا تریس می‌فرستد. 2. **ایجنت‌های راه دور** روی سرورهای دیگر اجرا می‌شوند و لاگ کانتینرهایشان را از طریق WebSocket به collector استریم می‌کنند. 3. همه‌ی کانتینرها (در حال اجرا و متوقف) در پروژه‌ی شما ثبت می‌شوند تا بتوانید آن‌ها را ببینید و مدیریت کنید. 4. ارتباط دوطرفه است، پس دستورهای کانتینر (start، stop، restart، حذف، آمار) به هر سروری می‌رسند. اگر فقط یک سرور دارید، همان collector کافی است؛ حالت پیش‌فرض هم همین است.

راه‌اندازی روی یک سرور

```bash docker run -d \ --name abrova-trace-agent \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v abrova-trace-agent-data:/var/lib/abrova-trace-agent \ -e ABROVA_TRACE_API_KEY=ab_srv_your_server_secret \ ghcr.io/abrova-co/abrova-trace-agent:latest ``` ایمیج ایجنت از `ghcr.io` (رجیستری کانتینر GitHub) دریافت می‌شود، پس سرور باید به آن دسترسی داشته باشد. اگر دسترسی ندارد، ایمیج را روی دستگاهی که دسترسی دارد pull کنید و بعد با `docker save ghcr.io/abrova-co/abrova-trace-agent:latest -o agent.tar` و `docker load -i agent.tar` منتقلش کنید. این volume بافر دیسک ایجنت و موقعیت‌های خوانده‌شده را هنگام ساخت دوباره‌ی کانتینر نگه می‌دارد تا هیچ خطی گم نشود یا دو بار فرستاده نشود. **کلید سرور** پروژه (`ab_srv_...`) را در `ABROVA_TRACE_API_KEY` به ایجنت بدهید. کلید اپ (`ab_live_...`) هم تا وقتی تنظیم پروژه‌ی **Server routes require the server secret** خاموش است کار می‌کند، اما این کلید داخل اپ‌های شما منتشر می‌شود و نباید ایجنت با آن اجرا شود. [کلید سرور و مسیرهای سرور](/docs/trace/server-secret) را ببینید. ایجنت ظرف چند ثانیه همه‌ی کانتینرهای در حال اجرا و متوقف را در پروژه‌ی شما ثبت می‌کند. در کنسول ابرووا، **ابرووا تریس › Logs** را باز کنید تا رسیدن لاگ‌ها را ببینید.

راه‌اندازی چند سرور

برای چند سرور، روی هر سرور اضافه یک **ایجنت راه دور** راه بیندازید و یک **collector** را به آن‌ها وصل کنید. #### ایجنت راه دور (روی هر سرور اضافه) ```bash docker run -d \ --name abrova-trace-agent \ --restart=always \ -p 7008:7008 \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -e ABROVA_TRACE_HOSTNAME=server-2 \ ghcr.io/abrova-co/abrova-trace-agent:latest agent ``` #### collector (روی سرور اصلی) ```bash docker run -d \ --name abrova-trace-agent \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v abrova-trace-agent-data:/var/lib/abrova-trace-agent \ -e ABROVA_TRACE_API_KEY=ab_srv_your_server_secret \ -e ABROVA_TRACE_REMOTE_AGENTS=server2:YOUR_SERVER2_HOST:7008,server3:YOUR_SERVER3_HOST:7008 \ ghcr.io/abrova-co/abrova-trace-agent:latest ``` مقدار `ABROVA_TRACE_REMOTE_AGENTS` فهرستی از `name:host:port` است که با کاما جدا شده‌اند. هر ایجنت راه دور لاگ‌ها را از طریق WebSocket به collector استریم می‌کند و پس از قطع ارتباط خودش دوباره وصل می‌شود (هر ۵ ثانیه). [اپ دسکتاپ ابرووا](/desktop) (به‌زودی) ایجنت را از طریق SSH برایتان روی سرورها راه می‌اندازد: سرور را اضافه کنید، کلید میزبانش را تأیید کنید و راه‌اندازی کنید. تا آن زمان از دستورهای `docker run` همین صفحه استفاده کنید.

مدیریت کانتینرها از کنسول

در کنسول ابرووا، **ابرووا تریس › Logs** را باز کنید. پنل **منبع‌ها** همه‌ی سرورها و کانتینرهایشان را فهرست می‌کند؛ منوی کنار هر کانتینر این گزینه‌ها را دارد: - **مشاهدهٔ آمار** — پنجره‌ای زنده با درصد CPU، حافظه‌ی مصرفی / سقف حافظه و درصد حافظه، و RX / TX شبکه (بایت‌های دریافتی و ارسالی روی همه‌ی رابط‌های شبکه) - **شروع** — اجرای یک کانتینر متوقف - **restart** — راه‌اندازی دوباره در همان جا - **توقف** — توقف آرام یک کانتینر در حال اجرا - **حذف** — توقف و حذف کانتینر دستورها از طریق ابرووا تریس به WebSocket ایجنت می‌رسند، پس فرقی نمی‌کند کانتینر روی سرور اصلی باشد یا روی سروری با ایجنت راه دور. برای start، stop، restart و حذف، نقش **admin** یا **owner** لازم است؛ همه‌ی نقش‌ها می‌توانند کانتینرها، لاگ‌ها و آمار را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید.

مرجع پیکربندی

#### متغیرهای محیطی collector #### متغیرهای محیطی ایجنت راه دور #### پردازش لاگ (همه‌ی حالت‌ها)

تشخیص سطح لاگ

ایجنت برای هر ورودی یک سطح تعیین می‌کند تا فیلترهای سطح در کنسول بدون تغییر کد کار کنند: - **نشانه‌های صریح مقدم‌اند** — `[ERROR]`، `ERROR:`، `level=warn`، `[debug]` و موارد مشابه در ابتدای خط، همان سطحی را می‌گیرند که نوشته شده. `critical` و `panic` **fatal** حساب می‌شوند، `notice` **info** و `verbose` **trace**. - **لاگ‌های JSON** — فیلد `level`، `severity`، `lvl` یا `loglevel` استفاده می‌شود، از جمله سطح‌های عددی pino و bunyan. - **خط‌های NestJS** (`[Nest] PID - TIMESTAMP LEVEL ...`) سطحی را می‌گیرند که NestJS چاپ کرده. - **در غیر این صورت، کلیدواژه‌ها** — `fatal` یا `panic` ← fatal؛ `error`، `exception`، `failed`، `unhandled`، `uncaught`، `ECONNREFUSED`، `ETIMEDOUT` ← error؛ `warn`، `warning`، `deprecated` ← warn؛ `debug`، `dbg` ← debug. - **info** — پیش‌فرض وقتی هیچ‌کدام منطبق نباشد؛ پس `info: 0 errors found` همان info می‌ماند.

هر ورودی چه چیزهایی دارد

هر ورودی لاگ یک *رویداد* است، نه یک خط. علاوه بر پیام و سطح، فیلدهای اضافه‌ای در `metadata` دارد:

دسته‌بندی و ارسال

- **ارسال دسته‌ای** — لاگ‌ها در دسته‌های حداکثر ۱۰۰ ورودی یا هر ۵ ثانیه، هر کدام زودتر برسد، فرستاده می‌شوند. - **تلاش دوباره هنگام خطا** — دسته‌های ناموفق دوباره در صف قرار می‌گیرند و ارسالشان تکرار می‌شود؛ برای هر منبع تا ۵٬۰۰۰ ورودی در انتظار در حافظه نگه داشته می‌شود (بیش از آن، قدیمی‌ترین‌ها دور ریخته می‌شوند). - **بافر دیسک** — اگر ابرووا تریس در دسترس نباشد، دسته‌ها در `ABROVA_TRACE_DATA_DIR` نوشته می‌شوند و وقتی دوباره در دسترس شد به ترتیب ارسال می‌شوند (تا سقف `ABROVA_TRACE_SPOOL_MB`). - **ادامه پس از راه‌اندازی دوباره** — ایجنت آخرین خط خوانده‌شده‌ی هر کانتینر را به خاطر می‌سپارد و از همان‌جا ادامه می‌دهد. - **خاموش شدن آرام** — `docker stop abrova-trace-agent` پیش از خروج، دسته‌های در انتظار را می‌فرستد. ### ویژگی‌های اصلی ### مدل امنیتی ### عیب‌یابی لاگ ایجنت را بررسی کنید: `docker logs abrova-trace-agent`. علت‌های رایج: کلید نامعتبر، در دسترس نبودن آدرس API یا mount نشدن سوکت Docker. اگر تنظیم پروژه‌ی **Server routes require the server secret** روشن باشد و ایجنت با کلید اپ اجرا شود، خط‌های لاگ همچنان می‌رسند، اما ایجنت نمی‌تواند کانتینرها را ثبت کند، وضعیتشان را گزارش دهد یا دستور بگیرد. ایجنت را دوباره با مقدار کلید سرور (`ab_srv_...`) برای `ABROVA_TRACE_API_KEY` راه‌اندازی کنید. [کلید سرور و مسیرهای سرور](/docs/trace/server-secret) را ببینید. ایجنت کانتینرها را هنگام شروع ثبت می‌کند. اگر ایجنت را بعد از اجرای کانتینرها اضافه کرده‌اید و هنوز دیده نمی‌شوند، ایجنت را restart کنید: `docker restart abrova-trace-agent`. آمار از daemon محلی Docker ایجنتی خوانده می‌شود که کانتینر روی آن است. سروری که فقط در حالت `agent` اجرا می‌شود، لاگ‌هایش را به collector خودش استریم می‌کند؛ اگر آمار کانتینرهای آن خالی می‌ماند، بررسی کنید collector به آن وصل باشد (مورد بعدی را ببینید). مطمئن شوید پورت 7008 از سمت collector در دسترس است، مثلاً `ufw allow from COLLECTOR_IP to any port 7008`. قالب `ABROVA_TRACE_REMOTE_AGENTS` را دوباره بررسی کنید: `name:host:port`، جداشده با کاما. بررسی کنید کانتینرهایتان در فهرست کنارگذاشته‌ها (`ABROVA_TRACE_EXCLUDE`) نباشند و اگر `ABROVA_TRACE_INCLUDE` / `ABROVA_TRACE_LABEL_FILTER` را تنظیم کرده‌اید، با آن‌ها منطبق باشند. درست بودن کلید و دسترسی ایجنت به آدرس API را بررسی کنید. لاگ‌ها دسته‌ای فرستاده می‌شوند — تا ۵ ثانیه برای نمایششان صبر کنید. `ABROVA_TRACE_DROP` (ورودی‌های منطبق فرستاده نمی‌شوند) و `ABROVA_TRACE_HEALTH_SAMPLE` را بررسی کنید (به‌طور پیش‌فرض از هر ۲۰ خط health check موفق فقط یکی نگه داشته می‌شود؛ برای نگه داشتن همه، آن را `1` بگذارید). - لاگ در SDK فلاتر: https://abrova.ir/docs/trace/flutter-logging لاگ‌گیری کم‌مصرف برای اپ‌های Flutter با ارسال دسته‌ای خودکار، تلاش دوباره با فاصله‌ی افزایشی (exponential backoff) و توجه به چرخه‌ی حیات موبایل. لاگ‌ها در بافر نگه داشته و به‌صورت دسته‌ای ارسال می‌شوند تا مصرف شبکه کم بماند. لاگ‌ها (`AbrovaTrace.info()`، `warn()`، `logError()`، ...) در بافر نگه داشته، دسته‌ای ارسال و در **ابرووا تریس › Logs** نمایش داده می‌شوند. این‌ها از `AbrovaTrace.captureMessage()` جدا هستند؛ آن متد یک رویداد را همان لحظه به **ابرووا تریس › پیام‌ها** می‌فرستد. هیچ‌کدام مشکل (issue) نمی‌سازند؛ برای این کار از `captureException()` استفاده کنید.

فعال‌سازی لاگ‌گیری

پس از مقداردهی اولیه‌ی AbrovaTrace، سرویس لاگ‌گیری را فعال کنید: ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Initialize AbrovaTrace first await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', )); // Enable logging with a source identifier AbrovaTrace.enableLogging( sourceId: 'my-flutter-app', sourceName: 'My Flutter App', batchSize: 50, flushInterval: Duration(seconds: 5), ); ``` اگر یکی از متدهای لاگ را پیش از `enableLogging()` صدا بزنید، لاگ‌گیری خودکار با مقادیر پیش‌فرض و بدون `sourceId` فعال می‌شود. لاگ‌گیری به Flutter binding نیاز دارد، پس آن را پس از `WidgetsFlutterBinding.ensureInitialized()` فعال کنید (یا اولین لاگ را بعد از آن بفرستید).

ارسال لاگ‌ها

#### متدهای کمکی ```dart // Info log AbrovaTrace.info('User login successful', metadata: { 'user_id': 'user_123', }); // Warning log AbrovaTrace.warn('Slow API response', metadata: { 'endpoint': '/api/users', 'duration_ms': 2500, }); // Error log AbrovaTrace.logError('Database query failed', metadata: { 'query': 'SELECT * FROM users', 'error': 'Connection timeout', }); // Debug log AbrovaTrace.debug('Cache hit for key: user_123'); // Trace log AbrovaTrace.trace('Entering function processPayment()'); // Fatal log AbrovaTrace.fatal('App crashed unexpectedly', metadata: { 'last_screen': 'checkout', }); ``` #### متد عمومی لاگ ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Generic log with explicit level AbrovaTrace.log( 'Custom event', level: LogLevel.info, metadata: {'key': 'value'}, ); ```

سطح‌های لاگ

- **trace** — `AbrovaTrace.trace()` — دیباگ بسیار جزئی در سطح trace - **debug** — `AbrovaTrace.debug()` — اطلاعات دیباگ هنگام توسعه - **info** — `AbrovaTrace.info()` — اطلاعات عمومی اپلیکیشن - **warn** — `AbrovaTrace.warn()` — هشدار درباره‌ی مشکلات احتمالی - **error** — `AbrovaTrace.logError()` — خطاهایی که باید بررسی شوند - **fatal** — `AbrovaTrace.fatal()` — خطاهای بحرانی و غیرقابل بازیابی

رفتار در چرخه‌ی حیات موبایل

سرویس لاگ‌گیری رویدادهای چرخه‌ی حیات اپ را خودکار مدیریت می‌کند: - **توقف اپ / رفتن به پس‌زمینه** — همه‌ی لاگ‌های بافر را فوراً ارسال می‌کند - **بازگشت اپ** — شمارنده‌ی تلاش دوباره را صفر می‌کند و کار عادی ادامه پیدا می‌کند - **جدا شدن اپ (detached)** — لاگ‌ها را ارسال می‌کند و ارسال را متوقف می‌کند - **پر شدن بافر** — وقتی از سقف بافر (۱٬۰۰۰) بیشتر شود، قدیمی‌ترین لاگ‌ها حذف می‌شوند

عملیات دستی

پیکربندی

بافر حداکثر ۱٬۰۰۰ لاگ نگه می‌دارد (قدیمی‌ترین‌ها اول حذف می‌شوند). این سقف ثابت است و با `enableLogging()` تغییر نمی‌کند. ### توصیه‌ها - لاگ در SDK اندروید: https://abrova.ir/docs/trace/android-logging لاگ‌گیری ساختاریافته برای اپ‌های Android با ارسال دسته‌ای خودکار، تلاش مجدد با فاصله‌ی افزایشی (exponential backoff) و آگاهی از چرخه‌ی حیات اپ. لاگ‌ها بافر می‌شوند و دسته‌ای فرستاده می‌شوند تا مصرف اینترنت و باتری کم بماند.

فعال کردن لاگ‌گیری

بعد از راه‌اندازی AbrovaTrace در کلاس `Application`، سرویس لاگ‌گیری را فعال کنید: ```kotlin import android.app.Application import ir.abrova.trace.sdk.AbrovaTrace import ir.abrova.trace.sdk.AbrovaTraceConfig class MyApp : Application() { override fun onCreate() { super.onCreate() // Initialize AbrovaTrace first val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY") .environment("production") .release(BuildConfig.VERSION_NAME) .build() AbrovaTrace.init(this, config) // Enable logging with a source identifier AbrovaTrace.enableLogging( sourceId = "my-android-app", sourceName = "My Android App", batchSize = 50, flushIntervalMs = 5000L ) } } ```

ارسال لاگ

#### متدهای میان‌بر ```kotlin import ir.abrova.trace.sdk.AbrovaTrace // Info log AbrovaTrace.info("User login successful", mapOf( "user_id" to "user_123" )) // Warning log AbrovaTrace.warn("Slow API response", mapOf( "endpoint" to "/api/users", "duration_ms" to 2500 )) // Error log AbrovaTrace.logError("Database query failed", mapOf( "query" to "SELECT * FROM users", "error" to "Connection timeout" )) // Debug log AbrovaTrace.logDebug("Cache hit for key: user_123") // Trace log AbrovaTrace.trace("Entering function processPayment()") // Fatal log AbrovaTrace.fatal("App crashed unexpectedly", mapOf( "last_screen" to "checkout" )) ``` #### متد عمومی log ```kotlin import ir.abrova.trace.sdk.models.LogLevel // Generic log with explicit level AbrovaTrace.log( "Custom event", LogLevel.INFO, mapOf("key" to "value") ) ```

سطح‌های لاگ

- **trace** — `AbrovaTrace.trace()` — دیباگ بسیار جزئی در سطح trace - **debug** — `AbrovaTrace.logDebug()` — اطلاعات دیباگ در زمان توسعه - **info** — `AbrovaTrace.info()` — اطلاعات عمومی اپ - **warn** — `AbrovaTrace.warn()` — هشدار درباره‌ی مشکلات احتمالی - **error** — `AbrovaTrace.logError()` — خطاهایی که باید بررسی شوند - **fatal** — `AbrovaTrace.fatal()` — خطاهای بحرانی و غیرقابل‌بازیابی

استفاده در Activityها

```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AbrovaTrace.info("MainActivity created", mapOf("screen" to "main")) } private fun handlePurchase() { AbrovaTrace.info("Purchase initiated", mapOf( "product_id" to "premium_pack", "amount" to 99.99 )) try { processPurchase() AbrovaTrace.info("Purchase completed", mapOf("amount" to 99.99)) } catch (e: Exception) { AbrovaTrace.logError("Purchase failed", mapOf( "error" to e.message, "product_id" to "premium_pack" )) AbrovaTrace.captureException(e) } } override fun onDestroy() { if (isFinishing) { AbrovaTrace.flushLogs() AbrovaTrace.close() } super.onDestroy() } } ```

عملیات دستی

if (success) { Log.d("AbrovaTrace", "Logs flushed successfully") } } // Check buffer status val pendingCount = AbrovaTrace.getPendingLogCount() Log.d("AbrovaTrace", "Pending logs: ${'$'}pendingCount")`} />

پیکربندی

اگر پیش از `enableLogging()` یکی از متدهای لاگ را صدا بزنید، لاگ‌گیری خودکار با پیش‌فرض‌های بالا (بدون `sourceId`) روشن می‌شود. بدون `sourceId` هر لاگ جداگانه به `/api/logs/ingest` فرستاده می‌شود و ارسال ناموفق دوباره امتحان نمی‌شود؛ با آن، لاگ‌های بافرشده در یک درخواست به `/api/logs/ingest/batch` می‌روند و دسته‌ی ناموفق دوباره فرستاده می‌شود. وقتی اپ به پس‌زمینه می‌رود هم لاگ‌ها flush می‌شوند. تا وقتی اپ در پس‌زمینه است تایمر flush کار نمی‌کند: لاگ‌ها وقتی فرستاده می‌شوند که به اندازه‌ی دسته برسند یا اپ به پیش‌زمینه برگردد. ### توصیه‌ها - لاگ در SDK آی‌اواس: https://abrova.ir/docs/trace/ios-logging لاگ‌گیری ساخت‌یافته برای اپ‌های iOS با ارسال دسته‌ای خودکار، backoff نمایی و آگاهی از چرخه‌ی عمر اپ. لاگ‌ها بافر می‌شوند و دسته‌دسته فرستاده می‌شوند تا مصرف شبکه و باتری کم بماند.

فعال کردن لاگ‌گیری

پس از مقداردهی AbrovaTrace، سرویس لاگ‌گیری را فعال کنید. (اگر بدون فعال کردن، یکی از متدهای لاگ را صدا بزنید، لاگ‌گیری خودکار و بدون `sourceId` روشن می‌شود.) لاگ‌ها در کنسول زیر **ابرووا تریس › Logs** نمایش داده می‌شوند؛ این‌ها از `captureMessage` جدا هستند که به **پیام‌ها** می‌رود. #### UIKit ```swift import UIKit import AbrovaTrace @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize AbrovaTrace first let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .build() AbrovaTrace.shared.initialize(config: config) // Enable logging with a source identifier AbrovaTrace.shared.enableLogging( sourceId: "my-ios-app", sourceName: "My iOS App", batchSize: 50, flushInterval: 5.0 ) return true } } ``` #### SwiftUI ```swift import SwiftUI import AbrovaTrace @main struct MyApp: App { init() { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .build() AbrovaTrace.shared.initialize(config: config) // Enable logging AbrovaTrace.shared.enableLogging( sourceId: "my-ios-app", sourceName: "My iOS App" ) } var body: some Scene { WindowGroup { ContentView() } } } ```

ارسال لاگ

#### متدهای میان‌بر ```swift // Info log AbrovaTrace.shared.info("User login successful", metadata: [ "user_id": "user_123" ]) // Warning log AbrovaTrace.shared.warn("Slow API response", metadata: [ "endpoint": "/api/users", "duration_ms": 2500 ]) // Error log AbrovaTrace.shared.logErrorMessage("Database query failed", metadata: [ "query": "SELECT * FROM users", "error": "Connection timeout" ]) // Debug log AbrovaTrace.shared.logDebugMessage("Cache hit for key: user_123") // Trace log AbrovaTrace.shared.trace("Entering function processPayment()") // Fatal log AbrovaTrace.shared.fatal("App crashed unexpectedly", metadata: [ "last_screen": "checkout" ]) ``` #### متد عمومی log ```swift // Generic log with explicit level AbrovaTrace.shared.log("Custom event", level: .info, metadata: ["key": "value"]) ```

سطح‌های لاگ

- **trace** — `AbrovaTrace.shared.trace()` — دیباگ بسیار جزئی در سطح trace - **debug** — `AbrovaTrace.shared.logDebugMessage()` — اطلاعات دیباگ در زمان توسعه - **info** — `AbrovaTrace.shared.info()` — اطلاعات عمومی اپ - **warn** — `AbrovaTrace.shared.warn()` — هشدار درباره‌ی مشکل‌های احتمالی - **error** — `AbrovaTrace.shared.logErrorMessage()` — خطاهایی که باید بررسی شوند - **fatal** — `AbrovaTrace.shared.fatal()` — خطاهای بحرانی یا غیرقابل بازیابی

استفاده در ViewControllerها

```swift class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() AbrovaTrace.shared.info("ViewController loaded", metadata: ["screen": "main"]) } func handlePurchase() { AbrovaTrace.shared.info("Purchase initiated", metadata: [ "product_id": "premium_pack", "amount": 99.99 ]) do { try processPurchase() AbrovaTrace.shared.info("Purchase completed", metadata: ["amount": 99.99]) } catch { AbrovaTrace.shared.logErrorMessage("Purchase failed", metadata: [ "error": error.localizedDescription, "product_id": "premium_pack" ]) AbrovaTrace.shared.captureError(error) } } func processPurchase() throws { /* ... */ } } ``` #### ویوهای SwiftUI ```swift struct ContentView: View { var body: some View { Button("Purchase") { AbrovaTrace.shared.info("Purchase button tapped") handlePurchase() } .onAppear { AbrovaTrace.shared.info("ContentView appeared") } } func handlePurchase() { // Purchase logic with logging } } ```

عملیات دستی

پیکربندی

### بهترین روش‌ها - لاگ در SDK لاراول: https://abrova.ir/docs/trace/laravel-logging لاگ‌گیری ساخت‌یافته برای Laravel از طریق کانال Monolog، با ارسال دسته‌ای و تلاش دوباره. لاگ‌ها زیر **ابرووا تریس › Logs** نمایش داده می‌شوند؛ این‌ها از `AbrovaTrace::captureMessage()` جدا هستند که به پیام‌ها می‌رود.

نصب

مخزن ابرووا را به `composer.json` اضافه کنید و بعد نصب کنید ([SDK لاراول](/docs/trace/laravel-sdk#installation) را ببینید): ```json "repositories": [ { "type": "composer", "url": "https://center.abrova.ir/api/packages/abrova/composer" } ] ``` ```bash composer require abrova-trace/laravel-sdk ``` کانال لاگ را به `config/logging.php` اضافه کنید: ```php 'channels' => [ // ... other channels 'abrovatrace' => [ 'driver' => 'custom', 'via' => AbrovaTrace\Laravel\Logging\AbrovaTraceLogChannel::class, ], ], ```

استفاده‌ی پایه

```php use Illuminate\Support\Facades\Log; // Send logs to AbrovaTrace Log::channel('abrovatrace')->info('User signed up', ['userId' => 123]); Log::channel('abrovatrace')->warning('Rate limit approaching', ['remaining' => 5]); Log::channel('abrovatrace')->error('Payment failed', [ 'orderId' => 'ORD-456', 'reason' => 'insufficient_funds', ]); ```

سطح‌های لاگ

```php use Illuminate\Support\Facades\Log; Log::channel('abrovatrace')->debug('Debug information'); Log::channel('abrovatrace')->info('User action completed'); Log::channel('abrovatrace')->notice('Approaching rate limit'); // Maps to 'info' Log::channel('abrovatrace')->warning('Slow query detected'); // Maps to 'warn' Log::channel('abrovatrace')->error('Failed to save data'); Log::channel('abrovatrace')->critical('Service unavailable'); // Maps to 'fatal' Log::channel('abrovatrace')->alert('Database connection lost'); // Maps to 'fatal' Log::channel('abrovatrace')->emergency('App crashed'); // Maps to 'fatal' ``` سطح‌های Monolog این‌طور به سطح‌های AbrovaTrace نگاشت می‌شوند: DEBUG=debug، INFO/NOTICE=info، WARNING=warn، ERROR=error، CRITICAL/ALERT/EMERGENCY=fatal.

لاگ‌گیری مستقیم با SDK

می‌توانید مستقیم از طریق facade ابرووا تریس هم لاگ بگیرید: ```php use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; use AbrovaTrace\Laravel\Logging\LogLevel; // Basic log AbrovaTrace::log('Server started', LogLevel::INFO); // With metadata AbrovaTrace::log('Order processed', LogLevel::INFO, [ 'orderId' => 'ORD-123', 'amount' => 99.99, ]); // User ID: logs carry the id from setUser() (set automatically by the middleware) AbrovaTrace::setUser(['id' => 'user_abc123']); AbrovaTrace::log('Payment declined', LogLevel::ERROR, [ 'reason' => 'card_expired', ]); // Convenience methods AbrovaTrace::info('User logged in'); AbrovaTrace::warn('API rate limit at 80%'); AbrovaTrace::logError('Failed to send email'); // also: trace(), logDebug(), fatal() ```

کانال stack

با اضافه کردن abrovatrace به یک کانال stack، لاگ‌ها را به چند مقصد بفرستید: ```php // config/logging.php 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['daily', 'abrovatrace'], 'ignore_exceptions' => false, ], 'abrovatrace' => [ 'driver' => 'custom', 'via' => AbrovaTrace\Laravel\Logging\AbrovaTraceLogChannel::class, ], ], ``` ```php // Now Log::info() sends to both daily file AND AbrovaTrace Log::info('User action', ['userId' => 123]); ``` لاراول exceptionهای گزارش‌شده را در کانال لاگ پیش‌فرض شما می‌نویسد و از نسخه‌ی 0.2.3 به بعد، SDK دیگر جلوی این کار را نمی‌گیرد. اگر `abrovatrace` در stack پیش‌فرض باشد، هر exception علاوه بر گزارش به‌عنوان خطا، به‌شکل یک ورودی لاگ هم در لاگ‌ها می‌رسد.

metadata و اطلاعات زمینه

```php // Structured metadata Log::channel('abrovatrace')->info('Order placed', [ 'orderId' => 'ORD-12345', 'amount' => 4500000, 'currency' => 'IRR', 'items' => 3, ]); // Automatically included with every log: // - Platform ('laravel'), environment name and release // - User id, when a user is set (the middleware sets the authenticated user) ```

پیکربندی

```php // config/abrovatrace.php 'logging' => [ 'source_id' => env('ABROVATRACE_LOG_SOURCE_ID', null), // Groups logs into one source; enables batch sending 'source_name' => env('ABROVATRACE_LOG_SOURCE_NAME', null), // Display name for the source 'batch_size' => 50, // Send when buffer reaches this size ], ``` ### بهترین روش‌ها - لاگ در SDK نود: https://abrova.ir/docs/trace/nodejs-logging لاگ‌گیری سمت سرور با تلاش مجدد با فاصله‌ی افزایشی (exponential backoff)، بدون هیچ وابستگی و با یکپارچگی Express.js. لاگ‌ها در **ابرووا تریس › Logs** نمایش داده می‌شوند و از `AbrovaTrace.captureMessage()` جدا هستند؛ آن متد به بخش Messages می‌رود.

نصب

رجیستری ابرووا را به `.npmrc` اضافه کنید و سپس نصب کنید (نگاه کنید به [SDK Node.js](/docs/trace/nodejs-sdk#installation)): ```bash # .npmrc @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` ```bash npm install @abrova-trace/nodejs-sdk ```

استفاده‌ی پایه

```javascript const { LogService, LogLevel } = require('@abrova-trace/nodejs-sdk'); const logger = new LogService({ apiKey: 'ab_live_your_api_key', sourceId: 'api-server', sourceName: 'My API Server', environment: 'production', }); logger.log('Server started on port 3000', LogLevel.INFO); logger.log('Database connection slow', LogLevel.WARN, { latency: 2500 }); logger.log('Failed to process payment', LogLevel.ERROR, { orderId: '12345' }); // Graceful shutdown process.on('SIGTERM', async () => { await logger.dispose(); process.exit(0); }); ``` اگر از قبل `AbrovaTrace.init()` را صدا می‌زنید، به‌جای ساختن یک `LogService` می‌توانید از همان برای لاگ‌گیری استفاده کنید. این روش کلید API، environment و release را از `init()` برمی‌دارد و کاربری را که با `setUser()` تنظیم کرده‌اید به لاگ‌ها پیوست می‌کند: ```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); AbrovaTrace.enableLogging({ sourceId: 'api-server', sourceName: 'My API Server' }); // flushes every 5s by default AbrovaTrace.info('Server started on port 3000'); AbrovaTrace.warn('Database connection slow', { latency: 2500 }); AbrovaTrace.logError('Failed to process payment', { orderId: '12345' }); // also: trace(), logDebug(), fatal(), log(message, level, metadata) await AbrovaTrace.flushLogs(); // sends buffered logs now ``` پیش از بسته شدن پروسه، `await AbrovaTrace.flush()` را صدا بزنید. این متد لاگ‌های بافرشده را همراه با خطاها، پیام‌ها و spanهای عملکرد در انتظار می‌فرستد و اگر زمان انتظارش (پیش‌فرض: ۵۰۰۰ میلی‌ثانیه) زودتر تمام شود، مقدار `false` برمی‌گرداند. متد `AbrovaTrace.close()` هم لاگ‌های بافرشده را می‌فرستد و سپس SDK را ریست می‌کند.

سطح‌های لاگ

```javascript const { LogLevel } = require('@abrova-trace/nodejs-sdk'); logger.log('Detailed trace info', LogLevel.TRACE); logger.log('Debug information', LogLevel.DEBUG); logger.log('User action completed', LogLevel.INFO); logger.log('Approaching rate limit', LogLevel.WARN); logger.log('Failed to save data', LogLevel.ERROR); logger.log('App crashed unexpectedly', LogLevel.FATAL); ```

یکپارچگی با Express.js

{ const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; const level = res.statusCode >= 400 ? LogLevel.ERROR : LogLevel.INFO; logger.log(\`\${req.method} \${req.path} \${res.statusCode}\`, level, { method: req.method, path: req.path, statusCode: res.statusCode, duration, }); }); next(); }); // Error logging middleware app.use((err, req, res, next) => { logger.log(err.message, LogLevel.ERROR, { stack: err.stack, path: req.path, }); res.status(500).json({ error: 'Internal Server Error' }); });`} />

متادیتا و اطلاعات کاربر

```javascript // Structured metadata logger.log('Order placed', LogLevel.INFO, { orderId: 'ORD-12345', amount: 4500000, currency: 'IRR', }); // Associate logs with a user (4th parameter) logger.log('Payment declined', LogLevel.ERROR, { reason: 'insufficient_funds', }, 'user_abc123'); ```

عملیات دستی

```javascript // Force flush all buffered logs await logger.flush(); // Check buffer status console.log('Pending logs:', logger.bufferSize); // Dispose when shutting down (flushes remaining logs) await logger.dispose(); ```

پیکربندی

```javascript const logger = new LogService({ apiKey: 'ab_live_your_api_key', // Required serverSecret: process.env.ABROVA_SERVER_SECRET, // Optional: ab_srv_..., sent with every request sourceId: 'api-server', // Needed for batch sending (without it, logs are sent one by one) sourceName: 'My API Server', // Optional display name platform: 'nodejs', // Default: 'nodejs' environment: 'production', // 'development', 'staging', 'production' release: '1.2.3', // Optional app version batchSize: 50, // Flush when buffer reaches this size flushIntervalMs: 30000, // Max time before flush (default: 30000) maxBufferSize: 1000, // Drop oldest logs when exceeded debug: false, // Enable console debug output }); ``` ### توصیه‌ها - لاگ در SDK نکست: https://abrova.ir/docs/trace/nextjs-logging لاگ‌گیری فول‌استک با TypeScript برای اپ‌های Next.js، با پشتیبانی از App Router و Pages Router.

نصب

رجیستری ابرووا را به `.npmrc` اضافه کنید و سپس نصب کنید (نگاه کنید به [SDK Next.js](/docs/trace/nextjs-sdk#installation)): ```bash # .npmrc @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` ```bash npm install @abrova-trace/nextjs-sdk ```

راه‌اندازی logger

فایل `lib/logger.ts` را بسازید: ```typescript import { LogService } from '@abrova-trace/nextjs-sdk'; export const logger = new LogService({ apiKey: process.env.ABROVA_TRACE_API_KEY!, sourceId: 'nextjs-app', sourceName: 'My Next.js App', platform: 'nextjs', environment: process.env.NODE_ENV, }); ```

App Router (نسخه‌ی ۱۳ به بعد Next.js)

```typescript // app/api/users/route.ts import { NextResponse } from 'next/server'; import { logger } from '@/lib/logger'; export async function GET(request: Request) { logger.log('Fetching users', 'info'); try { const users = await db.users.findMany(); logger.log('Users fetched', 'info', { count: users.length }); return NextResponse.json(users); } catch (error) { logger.log('Failed to fetch users', 'error', { error: error instanceof Error ? error.message : 'Unknown', }); return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); } } ``` ### Pages Router (API Routeها) ```typescript // pages/api/orders.ts import { logger } from '@/lib/logger'; export default async function handler(req, res) { logger.log('Order request received', 'info', { method: req.method, path: req.url, }); try { const order = await createOrder(req.body); logger.log('Order created', 'info', { orderId: order.id }); res.json(order); } catch (error) { logger.log('Order creation failed', 'error', { error: error.message, }); res.status(500).json({ error: 'Failed to create order' }); } } ```

سطح‌های لاگ

```typescript // LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' logger.log('Detailed trace info', 'trace'); logger.log('Debug information', 'debug'); logger.log('User action completed', 'info'); logger.log('Approaching rate limit', 'warn'); logger.log('Failed to save data', 'error'); logger.log('App crashed unexpectedly', 'fatal'); ```

یکپارچگی با middleware

```typescript // middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { logger } from '@/lib/logger'; export function middleware(request: NextRequest) { const start = Date.now(); logger.log('Request received', 'info', { method: request.method, path: request.nextUrl.pathname, }); const response = NextResponse.next(); const duration = Date.now() - start; logger.log('Request processed', 'info', { path: request.nextUrl.pathname, duration, }); return response; } export const config = { matcher: '/api/:path*' }; ```

لاگ‌گیری سمت کلاینت

در مرورگر می‌توانید مستقیماً با همان نمونه‌ی SDK که راه‌اندازی کرده‌اید لاگ بگیرید، مثلاً `AbrovaTrace.getInstance()?.info('Button clicked', { buttonId: 'submit-form' })`. اگر ترجیح می‌دهید لاگ‌های کلاینت اول روی سرور خودتان بررسی یا فیلتر شوند، آن‌ها را مثل نمونه‌ی زیر از یک API route بفرستید. لاگ‌ها بافر می‌شوند (به‌طور پیش‌فرض بعد از ۵۰ لاگ یا ۳۰ ثانیه فرستاده می‌شوند) و با بسته شدن صفحه خودکار ارسال نمی‌شوند؛ پس هر وقت لاگی باید همان لحظه برود، `AbrovaTrace.getInstance()?.flushLogs()` را صدا بزنید. یک route واسط (proxy) بسازید: ```typescript // app/api/log/route.ts import { NextResponse } from 'next/server'; import { logger } from '@/lib/logger'; export async function POST(request: Request) { const { message, level, metadata } = await request.json(); const validLevels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; const safeLevel = validLevels.includes(level) ? level : 'info'; logger.log(message, safeLevel, { ...metadata, source: 'client' }); return NextResponse.json({ success: true }); } ``` کامپوننت کلاینت: ```typescript 'use client'; async function logToServer(message: string, level: string, metadata?: object) { await fetch('/api/log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, level, metadata }), }); } // Usage logToServer('Button clicked', 'info', { buttonId: 'submit-form' }); ```

پیکربندی

### توصیه‌ها - REST API لاگ: https://abrova.ir/docs/trace/logs-rest-api لاگ‌ها را از هر اپلیکیشن، زبان یا فریم‌ورکی با HTTPS بفرستید. هر درخواست در هدر `x-api-key` به یک کلید پروژه نیاز دارد: وقتی از سرور می‌فرستید کلید سرور (`ab_srv_...`)، و وقتی از اپ می‌فرستید کلید اپ (`ab_live_...`). کلید اپ داخل اپ‌های موبایل و وب منتشر می‌شود، پس آن را عمومی در نظر بگیرید. روی سرورهای خودتان از **کلید سرور** در یک متغیر محیطی استفاده کنید و آن را از کد سمت کلاینت دور نگه دارید. هر دو کلید، صرف‌نظر از تنظیمات پروژه، روی دو endpoint زیر پذیرفته می‌شوند. [کلید سرور و مسیرهای سرور](/docs/trace/server-secret) را ببینید. لاگ‌ها تا ۱ گیگابایت در ماه رایگان هستند؛ بیش از آن، هزینه از کیف پول: هر گیگابایت ۱۵٬۰۰۰ تومان. لاگ‌ها ۳۰ روز نگه داشته می‌شوند. [قیمت‌ها](/pricing) را ببینید.

ارسال یک لاگ

-default" یا "unknown-default"'], ['sourceName', 'string', 'خیر', 'نام خوانای منبع'], ['timestamp', 'string', 'خیر', 'زمان با قالب ISO 8601 (پیش‌فرض: زمان دریافت)'], ['metadata', 'object', 'خیر', 'جفت‌های کلید-مقدار اضافه'], ['platform', 'string', 'خیر', 'برچسب پلتفرم که در نمای Logs نشان داده می‌شود'], ['environment', 'string', 'خیر', 'Environment (پیش‌فرض: production)'], ['release', 'string', 'خیر', 'نسخه‌ی release'], ['userId', 'string', 'خیر', 'کاربری که لاگ به او مربوط است'], ]} /> ```bash curl -X POST https://trace.abrova.ir/api/logs/ingest \ -H "x-api-key: $ABROVA_TRACE_SERVER_SECRET" \ -H "Content-Type: application/json" \ -d '{ "sourceId": "api-server", "sourceName": "API Server", "level": "info", "message": "User login successful", "metadata": { "userId": "user_123", "ip": "192.168.1.1" } }' ``` پاسخ (`201`): {`{"status": "created", "id": 12345}`}

ارسال دسته‌ای

برای حجم بالای لاگ کارآمدتر است: چندین مورد را در یک درخواست، به‌صورت آرایه‌ی JSON زیر `logs` بفرستید. اینجا `sourceId` **الزامی** است؛ هر مورد در `logs` همان فیلدهای یک لاگ تکی را می‌پذیرد (به‌جز `sourceId` / `sourceName`). از هر درخواست حداکثر ۱٬۰۰۰ مورد ذخیره می‌شود؛ بیشتر از آن را در چند دسته بفرستید. ```bash curl -X POST https://trace.abrova.ir/api/logs/ingest/batch \ -H "x-api-key: $ABROVA_TRACE_SERVER_SECRET" \ -H "Content-Type: application/json" \ -d '{ "sourceId": "api-server", "logs": [ { "level": "info", "message": "Request received: GET /api/users" }, { "level": "debug", "message": "Querying database..." }, { "level": "info", "message": "Response sent: 200 OK (45ms)" } ] }' ``` پاسخ (`201`): {`{"status": "created", "count": 3, "projectId": "..."}`}

نمونه کدها

#### Node.js در اپ‌های Node.js، [SDK برای Node.js](/docs/trace/nodejs-logging) ارسال دسته‌ای و تلاش دوباره را برایتان انجام می‌دهد. #### Python ```python import os import requests ABROVA_TRACE_SERVER_SECRET = os.environ.get('ABROVA_TRACE_SERVER_SECRET') # ab_srv_... ABROVA_TRACE_URL = 'https://trace.abrova.ir' def send_log(source_id, level, message, metadata=None): requests.post( f'{ABROVA_TRACE_URL}/api/logs/ingest', json={ 'sourceId': source_id, 'level': level, 'message': message, 'metadata': metadata or {}, }, headers={'x-api-key': ABROVA_TRACE_SERVER_SECRET}, timeout=5, ) # Usage send_log('worker', 'info', 'Job started') send_log('worker', 'error', 'Job failed', {'job_id': '123'}) ``` #### Go ```go package main import ( "bytes" "encoding/json" "net/http" "os" ) func sendLog(sourceId, level, message string, metadata map[string]interface{}) error { body, _ := json.Marshal(map[string]interface{}{ "sourceId": sourceId, "level": level, "message": message, "metadata": metadata, }) req, _ := http.NewRequest("POST", "https://trace.abrova.ir/api/logs/ingest", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("x-api-key", os.Getenv("ABROVA_TRACE_SERVER_SECRET")) _, err := http.DefaultClient.Do(req) return err } ``` #### PHP در اپ‌های Laravel از [کانال لاگ در SDK برای Laravel](/docs/trace/laravel-logging) استفاده کنید. برای PHP ساده: ```php $ch = curl_init('https://trace.abrova.ir/api/logs/ingest'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-api-key: ' . getenv('ABROVA_TRACE_SERVER_SECRET'), ], CURLOPT_POSTFIELDS => json_encode([ 'sourceId' => 'shop-backend', 'level' => 'error', 'message' => 'payment callback timeout order=ORD-8812', ]), ]); curl_exec($ch); curl_close($ch); ```

کدهای پاسخ

- **201** — ساخته شد - **400** — بدنه‌ی درخواست نامعتبر است (`message` نیامده؛ در ارسال دسته‌ای، `logs` خالی است یا `sourceId` نیامده) - **401** — کلید نیامده یا نامعتبر است، یا ابرووا تریس برای پروژه فعال نیست - **429** — درخواست‌ها بیش از حد است. برای هر کلید و هر آدرس کلاینت سقف بالایی وجود دارد؛ کلاینتی که از آن بگذرد پاسخ `429` با هدر `Retry-After` (به ثانیه) می‌گیرد. به‌جای لاگ تکی دسته‌ای بفرستید و پیش از تلاش دوباره صبر کنید. - **500** — خطای سرور؛ بعداً دوباره تلاش کنید. - فیلتر و جست‌وجوی لاگ: https://abrova.ir/docs/trace/log-search در صفحه‌ی **ابرووا تریس › Logs** در کنسول ابرووا، لاگ‌ها را با فیلترهای قدرتمند سریع پیدا کنید. سطح لاگ، منبع، environment، بازه‌ی زمانی و جستجوی متنی را با هم ترکیب کنید تا دقیقاً به آنچه لازم دارید برسید. ### فیلتر بر اساس سطح لاگ برای نمایش یا پنهان کردن هر سطح، روی دکمه‌های سطح بالای فهرست لاگ کلیک کنید؛ دست‌کم یک سطح همیشه روشن می‌ماند. هر دکمه نشان می‌دهد چند خط از آن سطح در بازه‌ی فعلی هست. - **fatal** — کرش‌ها و خطاهای غیرقابل بازیابی - **error** — خطاهایی که باید فوراً بررسی شوند - **warning** — هشدار درباره‌ی مشکلات احتمالی (با نام `warn` ذخیره می‌شود) - **info** — اطلاعات عمومی درباره‌ی رفتار اپلیکیشن - **debug** — اطلاعات دقیق برای دیباگ - **trace** — لاگ‌های بسیار جزئی برای دیباگ عمیق ### فیلتر بر اساس منبع در پنل **منبع‌ها** یک منبع را انتخاب کنید تا فقط خط‌های آن را ببینید؛ با ⌘/Ctrl-کلیک منبع‌های بیشتری به انتخاب اضافه می‌شود (در گوشی از دکمه‌ی **همهٔ منبع‌ها** استفاده کنید). منبع‌ها با رسیدن لاگ به‌طور خودکار ساخته می‌شوند. نمونه‌ی منبع‌ها: `nginx`، `api-server`، `worker`، `database`، `cache`. ### جستجوی متنی جستجو به بزرگی و کوچکی حروف حساس نیست و با هر بخشی از پیام لاگ، metadata یا منبع آن تطبیق پیدا می‌کند. اگر بخواهید پیام را با یک عبارت باقاعده (regex) تطبیق دهید، گزینه‌ی regex را در کادر جستجو روشن کنید (به بزرگی و کوچکی حروف حساس نیست و در مرورگر شما روی خط‌هایی که قبلاً بارگذاری شده‌اند اعمال می‌شود). - `timeout` — همه‌ی لاگ‌هایی که «timeout» در آن‌ها آمده - `user_123` — لاگ‌های یک کاربر مشخص - `500` — خطاهای HTTP 500 - `connection refused` — خطاهای اتصال ### بازه‌ی زمانی برای محدود کردن جستجو یکی از بازه‌های آماده را انتخاب کنید: - **۱ ساعت اخیر** (پیش‌فرض) - **۲۴ ساعت اخیر** - **۷ روز اخیر** - **۳۰ روز اخیر** لاگ‌ها ۷ روز نگه داشته می‌شوند، مگر اینکه نگهداری ۳۰ روزه را اضافه کنید (برای هر پروژه ماهانه ۵۰٬۰۰۰ تومان اضافه، هزینه از کیف پول)؛ بنابراین بازه‌های طولانی‌تر فقط لاگ‌هایی را نشان می‌دهند که هنوز نگه داشته شده‌اند. برای محدود کردن به یک environment از منوی **Environment** استفاده کنید. ### قابلیت‌های داشبورد - **تعداد هر سطح** — هر دکمه‌ی سطح، تعداد خط‌های آن سطح را در یک نگاه نشان می‌دهد - **پخش زنده** — خط‌های جدید همان لحظه‌ی رسیدن نمایش داده می‌شوند (۱٬۰۰۰ خط آخر روی صفحه می‌ماند)؛ برای اسکرول و بارگذاری خط‌های قدیمی‌تر، پخش را متوقف کنید - **نمای یکپارچه و ستونی** — یک جریان ادغام‌شده را بخوانید، یا تا ۴ منبع را کنار هم مقایسه کنید - **جزئیات لاگ** — روی هر لاگ کلیک کنید تا پیام کامل و metadata آن را ببینید ذخیره‌ی یک جستجو به نقش member یا بالاتر نیاز دارد؛ حذف لاگ‌ها یا منبع‌ها به نقش admin یا owner نیاز دارد. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. - قوانین نادیده‌گرفتن: https://abrova.ir/docs/trace/ignore-rules قوانین نادیده‌گیری برای همه‌ی پروژه‌ها رایگان است. خطاهای حذف‌شده هیچ‌وقت مشکل جدید نمی‌شوند، پس از سهمیه‌ی رایگان یا کیف پول شما کم نمی‌کنند. ### قوانین نادیده‌گیری هر قانون الگویی است که روی **پیام خطای** هر خطای جدیدی که SDKها می‌فرستند آزموده می‌شود. قوانین متعلق به پروژه‌ی ابرووا هستند، پس هر قانون روی همه‌ی پلتفرم‌های آن (iOS، Android، Flutter، وب، سرور) اعمال می‌شود. #### ساختن قانون نادیده‌گیری 1. در کنسول ابرووا، **ابرووا تریس ← قوانین نادیده‌گرفتن** را باز کنید 2. روی **«قانون جدید»** کلیک کنید 3. یک نوع **تطبیق** انتخاب کنید (شامل، دقیق یا Regex) و **الگوی پیام** را بنویسید 4. پیش‌نمایش را بررسی کنید: نشان می‌دهد این الگو با چند مشکل از ۳۰ روز گذشته جور می‌شد (در مرورگر شما روی آخرین مشکل‌هایتان آزموده می‌شود) 5. روی **«ذخیرهٔ قانون»** کلیک کنید مدیریت قوانین (افزودن، ویرایش، روشن و خاموش کردن، حذف) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند آن‌ها را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. #### انواع تطبیق تطبیق به بزرگی و کوچکی حروف حساس نیست.
{['نوع', 'توضیح', 'مثال'].map((h) => ( ))} {[ ['شامل (Contains)', 'الگو هر جایی از پیام بیاید', 'ResizeObserver loop matches "ResizeObserver loop limit exceeded"'], ['دقیق (Exact)', 'کل پیام باید با الگو برابر باشد', 'Network Error matches only "Network Error"'], ['Regex', 'عبارت باقاعده‌ی جاوااسکریپت که روی پیام آزموده می‌شود', '^ChunkLoadError: Loading chunk \\d+ failed'], ].map(([type, desc, ex]) => ( ))}
{h}
{type} {desc} {ex}
قوانین فقط با پیام خطا تطبیق داده می‌شوند. تطبیق بر اساس URL، پلتفرم یا محیط در کنسول پشتیبانی نمی‌شود. #### مدیریت قوانین نادیده‌گیری - **روشن و خاموش کردن:** هر قانون یک سوییچ دارد، پس می‌توانید بدون حذف غیرفعالش کنید. - **ویرایش:** نوع تطبیق یا الگوی قانون را تغییر دهید. - **حذف:** قانون را حذف کنید؛ خطاهایی که با آن جور بودند دوباره به صندوق شما می‌رسند. خطاهای نادیده‌گرفته همان لحظه‌ی دریافت حذف می‌شوند: ذخیره نمی‌شوند، مشکلی نمی‌سازند یا به‌روز نمی‌کنند و هشداری هم ایجاد نمی‌کنند. قوانین فقط روی خطاهای جدید اثر دارند و مشکل‌های موجود حذف نمی‌شوند. پیام‌هایی که با captureMessage فرستاده می‌شوند تحت تأثیر قرار نمی‌گیرند. - حل‌کردن همه: https://abrova.ir/docs/trace/mark-all-resolved «حل کردن همه» برای همه‌ی پروژه‌ها رایگان است. ### حل کردن همه یک اقدام سریع برای این‌که همه‌ی خطاهای فعال را با یک کلیک حل‌شده علامت بزنید؛ مناسب برای بعد از deploy یا وقتی چند مشکل را با هم رفع کرده‌اید. #### نحوه‌ی استفاده 1. در کنسول ابرووا، **ابرووا تریس ← مشکلات** را باز کنید 2. منوی **عملیات بیشتر** (**⋯**) کنار عنوان صفحه را باز کنید و **«حل همه مشکلات باز…»** را انتخاب کنید 3. پنجره‌ی تأیید باز می‌شود؛ روی **«حل همه»** کلیک کنید 4. همه‌ی مشکل‌های باز و در حال بررسی پروژه (در همه‌ی پلتفرم‌ها) حل‌شده علامت می‌خورند. فیلترهای فعلی فهرست اعمال نمی‌شوند. این اقدام دست‌کم نقش member می‌خواهد؛ viewerها آن را نمی‌بینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. #### آشنایی با وضعیت خطاها
{[ { state: 'باز / در حال بررسی', color: 'bg-red-900/20 border-red-200', desc: 'مشکل‌هایی که باید بررسی شوند. مشکل‌های جدید با وضعیت باز شروع می‌شوند و در نمای پیش‌فرض مشکل‌ها دیده می‌شوند.' }, { state: 'حل‌شده', color: 'bg-emerald-900/20 border-emerald-800', desc: 'مشکل‌هایی که رفع شده‌اند. رویدادهای جدید همچنان شمارششان را بالا می‌برند، اما مشکل حل‌شده باقی می‌ماند.' }, { state: 'نادیده‌گرفته / بایگانی‌شده', color: 'bg-orange-900/20 border-orange-800', desc: 'مشکل‌هایی که کنار گذاشته‌اید. هر مشکلی را می‌توانید از منوی وضعیتش دوباره باز کنید.' }, ].map((s) => (
{s.state}

{s.desc}

))}
#### چه زمانی استفاده کنیم #### وقتی همه را حل‌شده می‌کنید چه اتفاقی می‌افتد؟
  • {'✓'} همه‌ی مشکل‌های باز و در حال بررسی به وضعیت «حل‌شده» می‌روند
  • {'✓'} اعلان‌های یکپارچگی‌ها (Slack، ایمیل و غیره) فقط برای مشکل‌های کاملاً جدید فرستاده می‌شوند، پس مشکل‌های حل‌شده دوباره اعلان نمی‌دهند
  • {'✓'} تاریخچه‌ی خطا و تعداد رخدادها حفظ می‌شود
  • {'✓'} مشکل‌های حل‌شده را با فیلتر وضعیت در صفحه‌ی مشکل‌ها می‌توانید ببینید
  • ! اگر خطا دوباره رخ دهد، شمارش و زمان آخرین دیده شدنش به‌روز می‌شود اما خودکار دوباره باز نمی‌شود؛ در صورت نیاز خودتان بازش کنید
#### حل کردن تکی در برابر گروهی
{['اقدام', 'کِی استفاده کنیم', 'چطور'].map((h) => ( ))}
{h}
حل کردن یک خطا یک مشکل مشخص را رفع کرده‌اید مشکل را باز کنید ← «حل شد» (میان‌بر R)
حل کردن همه چند رفع با هم deploy شده‌اند مشکل‌ها ← ⋯ ← «حل همه مشکلات باز…»
#### بهترین روش‌ها - گزارش هفتگی ایمیلی: https://abrova.ir/docs/trace/weekly-digest گزارش هفتگی ایمیلی بدون هزینه‌ی اضافه جزو ابرووا تریس است. [قیمت‌ها](/pricing) را ببینید. ### گزارش هفتگی ایمیلی هر دوشنبه ساعت ۹ صبح به وقت نیویورک، خلاصه‌ای ایمیلی از خطاهای هفته‌ی گذشته می‌گیرید: مشکل‌های جدید، حل‌شده و باز، تعداد رخدادها، روند نسبت به هفته‌ی قبل و پرتکرارترین خطاها. بدون این‌که مدام داشبورد را چک کنید، از سلامت اپلیکیشن خود باخبر بمانید. #### در گزارش چه هست؟ #### نمونه‌ی ایمیل گزارش

موضوع: {'\u{1F4CA}'} گزارش هفتگی خطاهای شما - ۱۵ دسامبر

{'\u{1F4CA}'} گزارش هفتگی خطاهای شما

هفته‌ی ۱۵ دسامبر ۲۰۲۵

۸

خطای جدید

۱۲

حل‌شده

۵

باز

۴۷

رخداد

{'↑'} ۳۳٪ بیشتر از هفته‌ی قبل

پرتکرارترین خطاهای این هفته:

۱. TypeError: Cannot read 'map' of undefined — ۲۳ · باز

۲. NetworkError: Failed to fetch — ۱۵ · حل‌شده

۳. ReferenceError: user is not defined — ۹ · باز

#### چطور کار می‌کند 1. **جمع‌آوری خودکار:** ابرووا تریس در طول هفته همه‌ی داده‌های خطا را خودکار جمع می‌کند 2. **پردازش هفتگی:** هر دوشنبه ساعت ۹ صبح به وقت نیویورک (بعدازظهر به وقت تهران) آمار خطاهای همه‌ی پروژه‌های شما جمع‌بندی می‌شود 3. **ارسال ایمیل:** خلاصه‌ای قالب‌بندی‌شده به آدرس ایمیل صاحب پروژه فرستاده می‌شود #### مدیریت گزارش گزارش خودکار فرستاده می‌شود؛ در کنسول ابرووا تنظیم جداگانه‌ای برای روشن یا خاموش کردنش وجود ندارد. گزارش حتی در هفته‌ای آرام هم فرستاده می‌شود؛ اگر خطایی نبوده باشد، بخش پرتکرارترین خطاها همین را می‌گوید. - هشدار جهش: https://abrova.ir/docs/trace/spike-detection هشدار جهش خطا بدون هزینه‌ی اضافه جزو ابرووا تریس است. [قیمت‌ها](/pricing) را ببینید. ### تشخیص جهش وقتی تعداد مشکل‌های جدید در یک ساعت گذشته به ۲۰۰٪ (۲ برابر) میانگین ساعتی یا بیشتر برسد، خودکار تشخیص داده می‌شود و هشداری از طریق یکپارچگی‌های پروژه فرستاده می‌شود؛ تا پیش از این‌که جهش روی کاربران بیشتری اثر بگذارد بررسی کنید. #### چطور کار می‌کند 1. **پایش مداوم**: ابرووا تریس هر ۱۵ دقیقه مشکل‌های جدید (خطاهایی که اولین بار دیده می‌شوند) را برای هر پروژه و پلتفرم می‌شمارد. 2. **مقایسه با میانگین**: تعداد مشکل‌های جدید در یک ساعت گذشته با میانگین ساعتی ۲۳ ساعت پیش از آن مقایسه می‌شود. 3. **تشخیص جهش**: اگر در یک ساعت گذشته دست‌کم ۳ مشکل جدید باشد و به ۲۰۰٪ (۲ برابر) میانگین یا بیشتر برسد، جهش تشخیص داده می‌شود. اگر اصلاً میانگینی وجود نداشته باشد، بیش از ۵ مشکل جدید در یک ساعت گذشته جهش حساب می‌شود. 4. **ارسال هشدار**: اعلانی به همه‌ی یکپارچگی‌های فعال پروژه (ایمیل، Slack، Discord، Telegram، Webhook، GitHub) فرستاده می‌شود. اگر هیچ یکپارچگی‌ای تنظیم نکرده باشید، هشداری فرستاده نمی‌شود. #### نمونه‌ی تصویری مشکل‌های جدید در یک ساعت گذشته ناگهان به ۵ می‌رسند، در حالی که میانگین ۱ در ساعت بوده است:
{[ { label: '۰۹:۰۰', h: 20, spike: false }, { label: '۱۰:۰۰', h: 20, spike: false }, { label: '۱۱:۰۰', h: 20, spike: false }, { label: '۱۲:۰۰', h: 20, spike: false }, { label: '۱۳:۰۰', h: 100, spike: true }, ].map((bar) => (
{bar.spike && جهش!}
{bar.label}
))}
عادی (حدود ۱ در ساعت)
جهش (۵ در ساعت)
۵ مشکل جدید در یک ساعت گذشته یعنی ۵۰۰٪ میانگین (۱ در ساعت). چون این از آستانه‌ی ۲۰۰٪ بیشتر است، هشدار فعال می‌شود. #### پیش‌نمایش ایمیل هشدار (یکپارچگی ایمیل)

موضوع: [AbrovaTrace] New Error: {'\u{1F6A8}'} Error Spike Detected: 500% increase in error rate

{'\u{1F6A8}'} Error Spike Detected: 500% increase in error rate

Project: My Flutter App

5

Occurrences (new issues, last hour)

~1

Baseline (avg/hour)

Error rate increased from ~1/hour to 5/hour

#### پیکربندی تشخیص جهش برای همه‌ی پروژه‌ها خودکار اجرا می‌شود. چیزی برای روشن کردن وجود ندارد. تنظیمات فعلی: آستانه، بازه یا کانال‌های خودتان را می‌خواهید؟ در **ابرووا تریس ← هشدارها** یک قانون **جهش خطا** بسازید. مدیریت قوانین هشدار نقش admin یا owner می‌خواهد؛ memberها می‌توانند هشدارها را تأیید و بی‌صدا کنند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. #### بهترین روش‌ها #### عیب‌یابی
  • مطمئن شوید پروژه دست‌کم یک یکپارچگی دارد و در ابرووا تریس ← یکپارچه‌سازی‌ها روشن است
  • با «ارسال تست» روی یکپارچگی مطمئن شوید پیام را می‌رساند
  • جهش‌ها مشکل‌های جدید را می‌شمارند، نه رویدادهای تکراری مشکلی که از قبل دارید؛ سیلی از یک خطای یکسان جهش حساب نمی‌شود
  • در یک ساعت گذشته دست‌کم ۳ مشکل جدید لازم است. پروژه‌ی کاملاً تازه هنوز میانگینی ندارد؛ در این حالت بیش از ۵ مشکل جدید در یک ساعت گذشته جهش حساب می‌شود
  • یکپارچگی‌هایی که به Slack، Discord، Telegram یا GitHub می‌فرستند به در دسترس بودن این سرویس‌ها نیاز دارند؛ صفحه‌ی هر یکپارچگی را ببینید
- مانیتور کران‌جاب: https://abrova.ir/docs/trace/cron-monitoring ۳ مانیتور cron رایگان در هر ماه. هر مانیتور اضافه ماهانه ۲٬۰۰۰ تومان هزینه از کیف پول دارد؛ [قیمت‌ها](/pricing) را ببینید. خود check-inها هزینه‌ای ندارند. check-inها کلید سرور شما (`ab_srv_…`) را در هدر `X-API-Key` می‌فرستند. آن‌ها را از سرور یا CI و با یک متغیر محیطی اجرا کنید؛ هرگز از داخل اپ. ### مانیتورینگ cron کارهای زمان‌بندی‌شده و cron jobها را با check-inهای HTTP مانیتور کنید. برای check-inهای نرسیده از طریق ایمیل، webhook یا یکپارچگی‌های دیگر باخبر می‌شوید؛ اجراهای ناموفق ثبت و روی مانیتور نمایش داده می‌شوند. SDK لازم نیست؛ هر کلاینت HTTP می‌تواند check-in بفرستد. #### چطور کار می‌کند 1. **ساختن مانیتور**: در کنسول ابرووا یک مانیتور cron با slug یکتا، زمان‌بندی (عبارت cron یا فاصله‌ی زمانی) و مهلت اضافه تعریف کنید. 2. **ارسال check-in**: cron job شما هر بار که اجرا می‌شود یک HTTP POST به API مربوط به check-in می‌فرستد. SDK لازم نیست؛ فقط یک فراخوانی ساده‌ی HTTP. 3. **تشخیص کارهای اجرانشده**: ابرووا تریس هر ۵ دقیقه همه‌ی مانیتورها را بررسی می‌کند. اگر check-in دیر شده باشد (بیشتر از فاصله‌ی مورد انتظار به‌علاوه‌ی مهلت اضافه)، وضعیت مانیتور به «missed» تغییر می‌کند. 4. **دریافت اعلان**: وقتی کاری اجرا نشده باشد، اعلان به یکپارچگی‌هایی که روی مانیتور انتخاب کرده‌اید (ایمیل، Webhook و غیره) فرستاده می‌شود. #### ساختن مانیتور (داشبورد) 1. در کنسول ابرووا، پروژه‌تان را باز کنید ← **ابرووا تریس ← Crons** 2. روی **«مانیتور جدید»** کلیک کنید 3. جزئیات مانیتور را پر کنید (پیکربندی پایین را ببینید) 4. در صورت تمایل، کانال‌های اعلان را از میان یکپارچگی‌های فعال انتخاب کنید 5. برای ذخیره روی **«ساخت مانیتور»** کلیک کنید مدیریت مانیتورها (ساختن، ویرایش، توقف موقت، حذف) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند مانیتورها و تاریخچه‌ی check-inهایشان را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. #### پیکربندی مانیتور #### ارسال check-in از کارها وقتی cron job اجرا می‌شود، یک HTTP POST به endpoint مربوط به check-in بفرستید. با **کلید سرور** پروژه (`ab_srv_...`) در هدر `X-API-Key` احراز هویت کنید. اگر slug هنوز وجود نداشته باشد، مانیتور **خودکار ساخته می‌شود**. cron jobها روی سرورهای شما اجرا می‌شوند، پس از کلید سرور استفاده کنید. کلید اپ (ab_live_...) هم تا وقتی تنظیم پروژه‌ی server routeها به server secret نیاز دارند خاموش است پذیرفته می‌شود؛ اما این کلید داخل اپ‌های شما منتشر می‌شود، پس هر کسی آن را استخراج کند می‌تواند check-in جعلی «ok» بفرستد. وقتی کارهایتان از کلید سرور استفاده کردند، این تنظیم را روشن کنید. کلید سرور و routeهای سرور را ببینید. اگر check-inی با slugی بفرستید که وجود ندارد، ابرووا تریس خودکار مانیتوری با تنظیمات پیش‌فرض (فاصله‌ی ۶۰ دقیقه، مهلت اضافه‌ی ۵ دقیقه) می‌سازد. بعد می‌توانید آن را در داشبورد شخصی‌سازی کنید. #### API مربوط به check-in
{['فیلد', 'نوع', 'الزامی', 'توضیح'].map((h) => ( ))} {[ ['monitor_slug', 'string', 'بله', 'slug یکتای مانیتور'], ['status', 'string', 'بله', '"ok"، "error" یا "in_progress"'], ['check_in_id', 'string', 'خیر', 'UUID برای دنبال کردن کارهای چندمرحله‌ای (به‌صورت `id` از in_progress برگردانده می‌شود)'], ['duration_ms', 'number', 'خیر', 'مدت اجرای کار بر حسب میلی‌ثانیه'], ['environment', 'string', 'خیر', 'نام محیط (مثلاً "production")'], ['message', 'string', 'خیر', 'پیام یا جزئیات خطا (اختیاری)'], ].map(([field, type, required, desc]) => ( ))}
{h}
{field} {type} {required === 'بله' ? بله : خیر} {desc}
#### مثال‌ها #### check-in ساده (curl) وقتی کار با موفقیت تمام شد، یک check-in با وضعیت «ok» بفرستید: ```bash curl -X POST https://trace.abrova.ir/api/cron-monitors/check-in \ -H "X-API-Key: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "monitor_slug": "daily-backup", "status": "ok", "duration_ms": 4500 }' ``` #### گزارش خطا (curl) اگر کار شکست خورد، آن را با وضعیت «error» گزارش کنید: ```bash curl -X POST https://trace.abrova.ir/api/cron-monitors/check-in \ -H "X-API-Key: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "monitor_slug": "daily-backup", "status": "error", "message": "Connection to database timed out" }' ``` #### کار چندمرحله‌ای (curl) برای کارهای طولانی، با «in_progress» شروع کنید و با «ok» یا «error» تمام کنید: #### Node.js ```javascript const axios = require('axios'); const API_KEY = process.env.ABROVA_SERVER_SECRET; // ab_srv_... const BASE_URL = 'https://trace.abrova.ir/api/cron-monitors/check-in'; async function runDailyBackup() { const start = Date.now(); try { // ... your backup logic here ... await performBackup(); // Report success await axios.post(BASE_URL, { monitor_slug: 'daily-backup', status: 'ok', duration_ms: Date.now() - start, }, { headers: { 'X-API-Key': API_KEY } }); } catch (error) { // Report failure await axios.post(BASE_URL, { monitor_slug: 'daily-backup', status: 'error', duration_ms: Date.now() - start, message: error.message, }, { headers: { 'X-API-Key': API_KEY } }); } } ``` #### Python ```python import os import requests import time API_KEY = os.environ["ABROVA_SERVER_SECRET"] # ab_srv_... BASE_URL = "https://trace.abrova.ir/api/cron-monitors/check-in" def run_daily_backup(): start = time.time() headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"} try: # ... your backup logic here ... perform_backup() # Report success requests.post(BASE_URL, json={ "monitor_slug": "daily-backup", "status": "ok", "duration_ms": int((time.time() - start) * 1000), }, headers=headers) except Exception as e: # Report failure requests.post(BASE_URL, json={ "monitor_slug": "daily-backup", "status": "error", "duration_ms": int((time.time() - start) * 1000), "message": str(e), }, headers=headers) ``` #### Laravel (دستور Artisan) performCleanup(); Http::withHeaders([ 'X-API-Key' => env('ABROVATRACE_SERVER_SECRET'), ])->post('https://trace.abrova.ir/api/cron-monitors/check-in', [ 'monitor_slug' => 'daily-cleanup', 'status' => 'ok', 'duration_ms' => (int)((microtime(true) - $start) * 1000), ]); } catch (\\Exception $e) { Http::withHeaders([ 'X-API-Key' => env('ABROVATRACE_SERVER_SECRET'), ])->post('https://trace.abrova.ir/api/cron-monitors/check-in', [ 'monitor_slug' => 'daily-cleanup', 'status' => 'error', 'message' => $e->getMessage(), ]); }`} /> #### اسکریپت Bash هر cron jobی را با یک فراخوانی ساده‌ی check-in در انتها بپوشانید: ```bash #!/bin/bash # Add this to the end of your cron job script API_KEY="$ABROVA_SERVER_SECRET" # ab_srv_... # Run your job /usr/local/bin/my-backup-script.sh # Report based on exit code if [ $? -eq 0 ]; then curl -s -X POST https://trace.abrova.ir/api/cron-monitors/check-in \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"monitor_slug": "my-backup", "status": "ok"}' else curl -s -X POST https://trace.abrova.ir/api/cron-monitors/check-in \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"monitor_slug": "my-backup", "status": "error"}' fi ``` #### وضعیت‌های مانیتور
{[ { status: 'OK', color: 'bg-emerald-900/20 border-emerald-800 text-emerald-400', desc: 'آخرین check-in موفق بوده است' }, { status: 'Error', color: 'bg-red-900/20 border-red-800 text-red-400', desc: 'آخرین check-in خطا گزارش کرده است' }, { status: 'Missed', color: 'bg-amber-900/20 border-amber-800 text-amber-400', desc: 'در زمان مورد انتظار هیچ check-inی نرسیده است' }, { status: 'Disabled', color: 'bg-white/[0.05] border-white/[0.08] text-zinc-500', desc: 'مانیتور موقتاً متوقف است' }, ].map((s) => (
{s.status}

{s.desc}

))}
#### مرجع API cron job شما check-inها را به یک endpoint عمومی می‌فرستد: ``` POST /api/cron-monitors/check-in ``` این endpoint یک اعتبارنامه‌ی پروژه را در هدر `X-API-Key` می‌گیرد: **کلید سرور** (توصیه‌شده)، یا کلید اپ تا وقتی تنظیم «server routeها به server secret نیاز دارند» خاموش است. مانیتورها را در کنسول ابرووا مدیریت کنید. این‌که کاربر واردشده آنجا چه کارهایی می‌تواند بکند به نقشش بستگی دارد؛ نقش‌ها و دسترسی‌ها را ببینید. #### بهترین روش‌ها #### عیب‌یابی
  • مطمئن شوید کلید درست و فعال است؛ وضعیت HTTP پاسخ check-in را ببینید؛ 401 یعنی رد شده است
  • مطمئن شوید monitor_slug دقیقاً یکی است
  • مطمئن شوید درخواست check-in به سرور می‌رسد (شبکه و firewall را بررسی کنید)
  • فاصله‌ی مورد انتظار را بازبینی کنید؛ اگر کار هر ۲۴ ساعت اجرا می‌شود، فاصله را ۱۴۴۰ دقیقه بگذارید
  • مهلت اضافه را بررسی کنید؛ اگر زمان شروع کار متغیر است آن را بیشتر کنید
  • مطمئن شوید روی مانیتور کانال اعلان انتخاب شده است
  • مطمئن شوید یکپارچگی‌های انتخاب‌شده فعال‌اند (در ابرووا تریس ← یکپارچه‌سازی‌ها تستشان کنید)
  • تشخیص کارهای اجرانشده هر ۵ دقیقه انجام می‌شود؛ ممکن است کمی تأخیر باشد
  • مطمئن شوید مانیتور فعال است (غیرفعال یا خاموش نشده باشد)
  • مطمئن شوید کلید با ab_srv_ (یا ab_live_) شروع می‌شود
  • اگر پاسخ server_credential_required می‌گوید، تنظیم پروژه‌ی server routeها به server secret نیاز دارند روشن است و کار کلید اپ را فرستاده؛ از کلید سرور استفاده کنید
  • مطمئن شوید کلید در هدر X-API-Key فرستاده می‌شود
  • مطمئن شوید کلید API متعلق به پروژه‌ی فعالی است که ابرووا تریس در آن فعال است
  • ۳ مانیتور رایگان است؛ مانیتورهای بیشتر از کیف پول سازمان پرداخت می‌شوند (ماهانه ۲٬۰۰۰ تومان برای هر مانیتور اضافه)
  • کیف پول را در کنسول ابرووا شارژ کنید، یا مانیتوری را که دیگر لازم ندارید حذف کنید
  • مانیتورهای خودکارساخته از مقادیر پیش‌فرض استفاده می‌کنند (فاصله‌ی ۶۰ دقیقه، مهلت ۵ دقیقه)
  • در کنسول ابرووا به ابرووا تریس ← Crons بروید و مانیتور را ویرایش کنید
  • یا از اول مانیتور را با تنظیمات دلخواه در داشبورد بسازید
- کلید سرور و مسیرهای سرور: https://abrova.ir/docs/trace/server-secret هر پروژه‌ی ابرووا دو اعتبارنامه دارد. این صفحه توضیح می‌دهد هر کدام کجا به کار می‌رود، و چطور routeهایی را که فقط سرورهای شما استفاده می‌کنند طوری تنظیم کنید که فقط کلید سرور را بپذیرند. ### دو اعتبارنامه هر دو در کنسول ابرووا در **تنظیمات پروژه ← کلیدها** هستند. کلید سرور فقط یک بار نمایش داده می‌شود، وقتی آن را می‌سازید یا عوض می‌کنید؛ آن را در secret manager یا محیط سرور خود نگه دارید. ### این تنظیم چه می‌کند **server routeها به server secret نیاز دارند** یک سوییچ برای هر پروژه است که در کنسول ابرووا در **تنظیمات پروژه ← محصولات ← ابرووا تریس** قرار دارد. به‌طور **پیش‌فرض خاموش** است. وقتی **روشن** باشد، routeهایی که فقط سرورها و agentها صدا می‌زنند فقط کلید سرور را می‌پذیرند. درخواستی که فقط کلید اپ را دارد آنجا با `401` رد می‌شود: برای اپ‌های شما چیزی تغییر نمی‌کند: خطاها، پیام‌ها، داده‌های عملکرد و لاگ‌هایی که با کلید اپ فرستاده می‌شوند، چه این تنظیم روشن باشد چه خاموش، کار می‌کنند. ### چرا روشنش کنیم کلید اپ ذاتاً عمومی است. بدون این تنظیم، کسی که آن را از اپ شما استخراج کرده می‌تواند: - یک check-in جعلی cron با پیام «کار موفق بود» بفرستد و کاری را که شکست می‌خورد یا اجرا نمی‌شود پنهان کند، - نشانگرهای deploy جعلی بنویسد، - به‌عنوان یک log agent جعلی وصل شود. با روشن بودن این تنظیم، کلید اپ برای هیچ‌کدام از این کارها کافی نیست. **وقتی روشنش کنید که** log agent اجرا می‌کنید، از مانیتورینگ cron استفاده می‌کنید یا نشانگر deploy می‌فرستید. پروژه‌ای که فقط از SDKهای موبایل و وب استفاده می‌کند به آن نیازی ندارد.

کلید سرور را به سرورهایتان بدهید

دو راه برای فرستادن کلید سرور روی HTTP وجود دارد. هر دو روی همه‌ی routeهای جدول بالا کار می‌کنند: - کلید سرور را **به‌جای کلید** بفرستید: `x-api-key: ab_srv_...` - کلید اپ را در `x-api-key` نگه دارید و کلید سرور را در هدر دوم بفرستید: `x-server-secret: ab_srv_...` #### Log agent agent یک اعتبارنامه می‌گیرد، در `ABROVA_TRACE_API_KEY`. آن را روی کلید سرور بگذارید و agent را دوباره deploy کنید: ```bash docker run -d \ --name abrova-trace-agent \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -e ABROVA_TRACE_API_KEY=ab_srv_your_server_secret \ ghcr.io/abrova-co/abrova-trace-agent:latest ``` در چیدمان چندسروری فقط collector اعتبارنامه را نگه می‌دارد؛ agentهای راه دور به تغییری نیاز ندارند. [اپ دسکتاپ](/desktop) ابرووا (به‌زودی) هم agent را deploy خواهد کرد و همین کلید را از شما می‌خواهد. [Abrova Trace Agent](/docs/trace/agent) را ببینید. #### check-inهای cron ```bash curl -X POST https://trace.abrova.ir/api/cron-monitors/check-in \ -H "X-API-Key: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{"monitor_slug": "daily-backup", "status": "ok"}' ``` [مانیتورینگ cron](/docs/trace/cron-monitoring) را ببینید. #### نشانگرهای deploy نشانگر deploy به ابرووا تریس می‌گوید «همین الان یک deploy انجام شد»، تا صفحه‌ی لاگ‌ها بتواند نشان دهد از آخرین deploy چه چیزی جدید است. آن را در انتهای اسکریپت deploy صدا بزنید. همه‌ی فیلدها اختیاری‌اند: ```bash curl -X POST https://trace.abrova.ir/api/logs/deploys \ -H "x-api-key: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "version": "1.4.2", "environment": "production", "source": "api-server", "description": "Checkout fixes" }' ``` پاسخ (`201`): {`{"success": true, "deploy": { ... }}`} #### لاگ‌هایی که از سرورها فرستاده می‌شوند [Logs REST API](/docs/trace/logs-rest-api) هیچ‌وقت به‌خاطر این تنظیم رد نمی‌شود، اما سرور بهتر است همچنان با کلید سرور احراز هویت کند، نه با کلید اپ. SDKهای Node.js و Laravel همین حالا کلید سروری را که با آن پیکربندی‌شان کرده‌اید می‌فرستند.

مطمئن شوید چیزی هنوز از کلید اپ استفاده نمی‌کند

در کنسول ابرووا، **تنظیمات پروژه ← محصولات ← ابرووا تریس** را باز کنید. خط زیر سوییچ نشان می‌دهد درخواست‌های اخیر روی routeهای سرور (در ۳۰ روز گذشته) چطور احراز هویت شده‌اند: - **«در ۳۰ روز گذشته، N درخواست در server routeها از کلید برنامه استفاده کرده‌اند.»**: چیزی هنوز کلید اپ را می‌فرستد. اول آن را پیدا کنید و به کلید سرور منتقل کنید. - **«آماده: در ۳۰ روز گذشته همهٔ درخواست‌های server routeها از server secret استفاده کرده‌اند.»**: روشن کردن امن است. - **«در ۳۰ روز گذشته هیچ درخواستی در server routeها نبوده است.»**: هنوز چیزی از این routeها استفاده نمی‌کند؛ روشن کردن روی کسی اثر ندارد.

روشن کردن

**server routeها به server secret نیاز دارند** را روشن کنید و ذخیره کنید. از آن به بعد، درخواستی روی route سرور که کلید سرور ندارد این پاسخ را می‌گیرد: ```json { "error": "Server credential required", "code": "server_credential_required" } ``` درخواستی که `x-server-secret` اشتباه بفرستد `401` با `"code": "server_credential_invalid"` می‌گیرد. هر وقت بخواهید می‌توانید این تنظیم را دوباره خاموش کنید؛ در این صورت کلید اپ مثل قبل روی این routeها پذیرفته می‌شود. ### عیب‌یابی

کار هنوز کلید اپ را می‌فرستد. در هدر X-API-Key آن را با کلید سرور (ab_srv_...) عوض کنید، یا هدر x-server-secret را اضافه کنید. تا آن موقع مانیتور کار را اجرانشده نشان می‌دهد.

agent هنوز با کلید اپ اجرا می‌شود. خطوط لاگ همچنان می‌رسند، اما agent دیگر نمی‌تواند کانتینرها را ثبت کند، وضعیتشان را بفرستد یا دستور بگیرد. آن را با مقدار کلید سرور در ABROVA_TRACE_API_KEY دوباره deploy کنید.

آن را همه‌جا به‌روز کنید: agent، cron jobها، اسکریپت‌های deploy و پیکربندی SDK بک‌اند. کلید قدیمی همان لحظه‌ی عوض کردن از کار می‌افتد.

- ایمیل: https://abrova.ir/docs/trace/email هشدارهای خطا را در صندوق ایمیل خود بگیرید. گیرندگان اعلان‌های ایمیلی ابرووا تریس را تنظیم کنید.
  • آدرس‌های ایمیلی که اعلان‌ها را دریافت می‌کنند
  • دسترسی owner یا admin به پروژه
ایمیل کانال پیش‌فرض خوبی در ایران است: به هیچ سرویس پیام‌رسانی بیرون از کشور نیاز ندارد و با هر صندوق ایمیلی که تیم شما استفاده می‌کند، از جمله سرویس‌های ایمیل ایرانی، کار می‌کند. این‌که پروژه‌ی شما از کدام کانال‌ها می‌تواند استفاده کند، در صفحه‌ی **ابرووا تریس ← یکپارچه‌سازی‌ها** در کنسول ابرووا نمایش داده می‌شود.

راه‌اندازی یکپارچگی ایمیل

1. در کنسول ابرووا، **ابرووا تریس ← یکپارچه‌سازی‌ها** را باز کنید (یکپارچگی‌ها متعلق به پروژه‌ی انتخاب‌شده‌اند) 2. روی **«افزودن یکپارچه‌سازی»** کلیک کنید و **ایمیل** را انتخاب کنید (یا روی کارت ایمیل **«اتصال»** را بزنید) 3. یک **نام** وارد کنید و فیلدهای پیکربندی را پر کنید 4. روی **«ذخیرهٔ کانال»** کلیک کنید، یا **«ذخیره و ارسال تست»** را بزنید تا ذخیره و بررسی شود مدیریت یکپارچگی‌ها (افزودن، ویرایش، روشن و خاموش کردن، حذف، تست) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند آن‌ها و لاگ ارسالشان را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. ### نمونه‌های پیکربندی - **یک گیرنده**: گیرنده: `dev@company.com` - **چند گیرنده**: گیرندگان: `dev@co.com`، `ops@co.com`، `team@co.com` - **فهرست توزیع**: گیرنده: `alerts@company.com` ### قالب ایمیل هر ایمیل اعلان شامل این‌هاست: - **موضوع**: `[AbrovaTrace] New Error: TypeError: Cannot read property 'map' of undefined` - پیام خطا و stack trace - نام پروژه و محیط - زمان اولین رخداد خطا - تعداد رخدادها - URL صفحه و user agent، اگر خطا آن‌ها را داشته باشد - لینکی برای دیدن خطا ایمیل‌های خطای جدید اولین باری که خطا دیده می‌شود فرستاده می‌شوند. تشخیص جهش هم به همین گیرندگان ایمیل می‌زند، و قوانین هشدار و مانیتورهای cron هم وقتی این کانال را برایشان انتخاب کنید. ### بهترین روش‌ها ### عیب‌یابی
  • پوشه‌ی spam یا junk را بررسی کنید
  • مطمئن شوید آدرس‌های ایمیل درست‌اند (بدون غلط تایپی)
  • مطمئن شوید یکپارچگی در ابرووا تریس ← یکپارچه‌سازی‌ها روشن است
  • یکپارچگی را با دکمه‌ی «ارسال تست» تست کنید
  • هر گیرنده باید آدرس معتبری باشد، مثلاً email1@domain.com
  • بعد از هر آدرس Enter بزنید یا ویرگول تایپ کنید تا اضافه شود
  • پیش از ذخیره، آدرس‌های قرمزشده را اصلاح یا حذف کنید
- وب‌هوک دلخواه: https://abrova.ir/docs/trace/webhooks داده‌های خطا را با webhookهای سفارشی به هر endpoint مربوط به HTTP بفرستید. با ابزارهای مدیریت حادثه، سیستم‌های داخلی یا هر چیزی که درخواست POST می‌پذیرد یکپارچه شوید.
  • یک endpoint مربوط به HTTP که درخواست POST بپذیرد
  • (اختیاری) یک کلید امضا برای بررسی درخواست‌ها
  • دسترسی owner یا admin به پروژه
webhook کانال پیش‌فرض خوبی در ایران است: آن را به سرور خودتان (یا ابزاری داخلی که روی اینترنت عمومی در دسترس است) وصل کنید تا هیچ سرویسی بیرون از کشور درگیر نشود. این‌که پروژه‌ی شما از کدام کانال‌ها می‌تواند استفاده کند، در صفحه‌ی **ابرووا تریس ← یکپارچه‌سازی‌ها** در کنسول ابرووا نمایش داده می‌شود.

راه‌اندازی یکپارچگی Webhook

1. در کنسول ابرووا، **ابرووا تریس ← یکپارچه‌سازی‌ها** را باز کنید (یکپارچگی‌ها متعلق به پروژه‌ی انتخاب‌شده‌اند) 2. روی **«افزودن یکپارچه‌سازی»** کلیک کنید و **Webhook** را انتخاب کنید (یا روی کارت Webhook **«اتصال»** را بزنید) 3. یک **نام** وارد کنید و فیلدهای پیکربندی را پر کنید 4. روی **«ذخیرهٔ کانال»** کلیک کنید، یا **«ذخیره و ارسال تست»** را بزنید تا ذخیره و بررسی شود #### قواعد URL - URL باید از `https://` استفاده کند و نباید نام کاربری یا رمز عبور داشته باشد. - host باید آدرس عمومی باشد. آدرس‌های خصوصی، loopback و دیگر آدرس‌های داخلی (مثلاً `localhost`، `10.x.x.x`، `192.168.x.x`) هنگام ذخیره رد می‌شوند و هرگز فراخوانی نمی‌شوند. - redirectها دنبال نمی‌شوند: اگر endpoint شما با `3xx` پاسخ دهد، ارسال ناموفق حساب می‌شود. URL نهایی را وارد کنید. #### بعد از ذخیره مدیریت یکپارچگی‌ها (افزودن، ویرایش، روشن و خاموش کردن، حذف، تست) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند آن‌ها و لاگ ارسالشان را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. URL و کلید امضا بعد از ذخیره دیگر هرگز نمایش داده نمی‌شوند: فرم آن‌ها را «تنظیم‌شده» نشان می‌دهد (برای URL فقط scheme و host). فیلد را دست نزنید تا مقدار ذخیره‌شده بماند، یا **جایگزینی** را بزنید تا مقدار تازه‌ای وارد کنید. اگر URL را به host دیگری تغییر دهید (یا scheme یا port را عوض کنید)، کلید امضا را هم دوباره وارد کنید؛ کلید ذخیره‌شده هرگز خودبه‌خود به مقصد جدیدی فرستاده نمی‌شود. تغییر فقط مسیر روی همان host آن را نگه می‌دارد. ### قالب payload وقتی خطایی اولین بار دیده می‌شود، ابرووا تریس یک درخواست POST از نوع JSON با این ساختار می‌فرستد: قوانین هشدار و مانیتورهای cron که به این webhook وصل‌اند به‌جای آن رویدادهای `alert.triggered` / `alert.resolved` می‌فرستند، با `data` شامل `rule_name`، `rule_type`، `metric_value`، `threshold`، `operator`، `window_minutes`، `project` و `environment`. #### امضا وقتی کلید امضا تنظیم شده باشد، هر درخواست این‌ها را دارد: - `X-AbrovaTrace-Signature: sha256=`: مقدار HMAC-SHA256 بدنه‌ی JSON با کلید شما - `X-AbrovaTrace-Timestamp`: زمان ارسال بر حسب میلی‌ثانیه ### بهترین روش‌ها - Telegram: https://abrova.ir/docs/trace/telegram هشدارهای خطا را فوراً با ربات Telegram بگیرید. یک ربات بسازید، chat ID خود را پیدا کنید و در چند دقیقه دریافت اعلان‌ها را شروع کنید. Telegram سرویسی بیرون از ایران است و ممکن است از برخی شبکه‌های داخل ایران در دسترس نباشد. برای راه‌اندازی و خواندن هشدارها به اتصالی نیاز دارید که به Telegram برسد، و پیام‌ها فقط وقتی می‌رسند که api.telegram.org پاسخ دهد؛ ارسال‌های ناموفق در لاگ ارسال یکپارچگی نمایش داده می‌شوند. اگر این برای تیم شما مشکل است، به‌جای آن یا در کنارش از یکپارچگی [ایمیل](/docs/trace/email) یا [Webhook](/docs/trace/webhooks) استفاده کنید. این‌که پروژه‌ی شما از کدام کانال‌ها می‌تواند استفاده کند، در صفحه‌ی **ابرووا تریس ← یکپارچه‌سازی‌ها** در کنسول ابرووا نمایش داده می‌شود.

ساختن ربات Telegram

1. Telegram را باز کنید و **@BotFather** را جست‌وجو کنید 2. دستور `/newbot` را بفرستید 3. یک نام برای ربات وارد کنید (مثلاً **«AbrovaTrace Alerts»**) 4. یک نام کاربری وارد کنید (باید با «bot» تمام شود، مثلاً **«MyAbrovaTrace_bot»**) 5. **Bot Token** را که BotFather می‌دهد کپی کنید قالب توکن: `1234567890:ABCdefGHIjklMNOpqrsTUVwxyz123456789` توکن ربات را محرمانه نگه دارید. هر کسی توکن را داشته باشد می‌تواند ربات شما را کنترل کند.

پیدا کردن Chat ID

#### گزینه‌ی الف: گفت‌وگوی شخصی 1. با ربات خود گفت‌وگو را شروع کنید (با نام کاربری جست‌وجویش کنید) 2. هر پیامی به ربات بفرستید 3. این URL را در مرورگر باز کنید: Chat ID را در پاسخ JSON زیر `message.chat.id` پیدا کنید. #### گزینه‌ی ب: گروه 1. یک گروه جدید بسازید یا از گروهی موجود استفاده کنید 2. ربات را به گروه اضافه کنید 3. در گروه یک پیام بفرستید 4. از همان URL مربوط به `getUpdates` در بالا استفاده کنید Chat ID گروه‌ها عدد منفی است (مثلاً `-1001234567890`).

پیکربندی در ابرووا تریس

1. در کنسول ابرووا، **ابرووا تریس ← یکپارچه‌سازی‌ها** را باز کنید (یکپارچگی‌ها متعلق به پروژه‌ی انتخاب‌شده‌اند) 2. روی **«افزودن یکپارچه‌سازی»** کلیک کنید و **Telegram** را انتخاب کنید (یا روی کارت Telegram **«اتصال»** را بزنید) 3. یک **نام**، Bot token و Chat ID را وارد کنید 4. روی **«ذخیره و ارسال تست»** کلیک کنید تا ذخیره و بررسی شود (یا **«ذخیرهٔ کانال»**) #### بعد از ذخیره مدیریت یکپارچگی‌ها (افزودن، ویرایش، روشن و خاموش کردن، حذف، تست) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند آن‌ها و لاگ ارسالشان را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. توکن ربات بعد از ذخیره دیگر هرگز نمایش داده نمی‌شود: فرم آن را «تنظیم‌شده» نشان می‌دهد. دست نزنید تا توکن ذخیره‌شده بماند، یا **جایگزینی** را بزنید تا توکن تازه‌ای وارد کنید. اگر Chat ID را تغییر دهید، توکن ربات را هم دوباره وارد کنید؛ توکن ذخیره‌شده هرگز خودبه‌خود برای گفت‌وگوی دیگری استفاده نمی‌شود. ### قالب اعلان پیام‌های Telegram از ابرووا تریس شامل این‌هاست: - سرتیتر **🚨 New Error Detected** - پیام خطا در یک بلوک monospace - پروژه، محیط، تعداد رخدادها و زمان اولین دیده شدن - ۵۰۰ کاراکتر اول stack trace - لینک **View in Dashboard** ### عیب‌یابی
  • اول باید با ربات گفت‌وگو را شروع کرده باشید
  • مطمئن شوید Bot Token درست است
  • مطمئن شوید Chat ID دقیق است
  • در گروه‌ها، مطمئن شوید ربات عضو گروه است
  • لاگ ارسال یکپارچگی را ببینید: timeout یا خطای اتصال یعنی Telegram در دسترس نبوده است
  • بعد از ساختن ربات به آن پیام بفرستید
  • چند ثانیه صبر کنید و URL را دوباره بارگذاری کنید
  • در گروه‌ها، بعد از اضافه کردن ربات پیام بفرستید
  • ممکن است Bot Token نامعتبر یا باطل شده باشد
  • با دستور /revoke در BotFather توکن تازه بسازید
  • یکپارچگی را در ابرووا تریس ← یکپارچه‌سازی‌ها ویرایش کنید و روی توکن ربات «جایگزینی» را بزنید
- Discord: https://abrova.ir/docs/trace/discord اعلان‌های لحظه‌ای خطا را با webhook در کانال‌های Discord بگیرید. پیام‌های embed غنی با جزئیات خطا، اطلاعات محیط و لینک مستقیم به داشبورد.
  • یک سرور Discord که در آن دسترسی admin دارید
  • یک کانال برای اعلان‌های خطا
  • حدود ۵ دقیقه برای راه‌اندازی
Discord سرویسی بیرون از ایران است و ممکن است از برخی شبکه‌های داخل ایران در دسترس نباشد. برای راه‌اندازی و خواندن هشدارها به اتصالی نیاز دارید که به Discord برسد، و پیام‌ها فقط وقتی می‌رسند که discord.com پاسخ دهد؛ ارسال‌های ناموفق در لاگ ارسال یکپارچگی نمایش داده می‌شوند. اگر این برای تیم شما مشکل است، به‌جای آن یا در کنارش از یکپارچگی [ایمیل](/docs/trace/email) یا [Webhook](/docs/trace/webhooks) استفاده کنید. این‌که پروژه‌ی شما از کدام کانال‌ها می‌تواند استفاده کند، در صفحه‌ی **ابرووا تریس ← یکپارچه‌سازی‌ها** در کنسول ابرووا نمایش داده می‌شود.

ساختن webhook در Discord

1. سرور Discord خود را باز کنید و به کانال مورد نظر بروید 2. روی کانال راست‌کلیک کنید ← **Edit Channel** 3. به زبانه‌ی **Integrations** بروید 4. روی **«Create Webhook»** یا **«New Webhook»** کلیک کنید 5. در صورت تمایل نام webhook را عوض کنید (مثلاً **«AbrovaTrace»**)؛ در هر حال پیام‌ها با نام و آواتار AbrovaTrace فرستاده می‌شوند 6. روی **«Copy Webhook URL»** کلیک کنید 7. روی **«Save Changes»** کلیک کنید

پیکربندی در ابرووا تریس

1. در کنسول ابرووا، **ابرووا تریس ← یکپارچه‌سازی‌ها** را باز کنید (یکپارچگی‌ها متعلق به پروژه‌ی انتخاب‌شده‌اند) 2. روی **«افزودن یکپارچه‌سازی»** کلیک کنید و **Discord** را انتخاب کنید (یا روی کارت Discord **«اتصال»** را بزنید) 3. یک **نام** وارد کنید و فیلدهای پیکربندی را پر کنید 4. روی **«ذخیرهٔ کانال»** کلیک کنید، یا **«ذخیره و ارسال تست»** را بزنید تا ذخیره و بررسی شود #### بعد از ذخیره مدیریت یکپارچگی‌ها (افزودن، ویرایش، روشن و خاموش کردن، حذف، تست) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند آن‌ها و لاگ ارسالشان را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. URL مربوط به webhook بعد از ذخیره دیگر هرگز نمایش داده نمی‌شود: فرم آن را «تنظیم‌شده» نشان می‌دهد و فقط scheme و host آن دیده می‌شود. دست نزنید تا URL ذخیره‌شده بماند، یا **جایگزینی** را بزنید تا URL تازه‌ای جای‌گذاری کنید. فقط آدرس‌های webhook خود Discord پذیرفته می‌شوند (`discord.com`، و `discordapp.com`، `ptb.discord.com` یا `canary.discord.com`). ### نمونه‌های پیکربندی - **هشدارهای production**: نام `#prod-errors`، با webhook در کانال production - **اعلان‌های توسعه**: نام `#dev-errors`، با webhook در کانال توسعه پیام‌ها با نام کاربری **Abrova Trace** فرستاده می‌شوند. ### قالب اعلان پیام‌های Discord از ابرووا تریس شامل این‌هاست: - عنوان **🚨 New Error: <error message>** (اولین باری که خطا دیده می‌شود فرستاده می‌شود) - پروژه، محیط و تعداد رخدادها، با زمان اولین دیده شدن به‌عنوان timestamp مربوط به embed - لینکی روی عنوان که به خطا برمی‌گردد تشخیص جهش هم اینجا پیام می‌گذارد، و قوانین هشدار و مانیتورهای cron هم وقتی این کانال را برایشان انتخاب کنید. ### بهترین روش‌ها ### عیب‌یابی
  • مطمئن شوید URL مربوط به webhook درست و کامل است
  • مطمئن شوید webhook در Discord حذف نشده است
  • مطمئن شوید یکپارچگی در ابرووا تریس ← یکپارچه‌سازی‌ها روشن است
  • یکپارچگی را با دکمه‌ی «ارسال تست» تست کنید
  • دسترسی‌های سرور و کانال را بررسی کنید
  • لاگ ارسال یکپارچگی را ببینید: timeout یا خطای اتصال یعنی Discord در دسترس نبوده است
  • در تنظیمات کانال Discord یک webhook تازه بسازید
  • یکپارچگی را در ابرووا تریس ← یکپارچه‌سازی‌ها ویرایش کنید و روی URL مربوط به webhook «جایگزینی» را بزنید
  • URLهای webhook را عمومی به اشتراک نگذارید
  • هر webhook به یک کانال مشخص وصل است
  • در کانال درست یک webhook تازه بسازید
  • یکپارچگی را در ابرووا تریس ← یکپارچه‌سازی‌ها ویرایش کنید و روی URL مربوط به webhook «جایگزینی» را بزنید
  • Discord برای webhookها محدودیت نرخ دارد
  • از گروه‌بندی خطاها برای کم کردن تکرار استفاده کنید
  • با قوانین نادیده‌گیری، خطاهای پرسروصدا را پیش از ارسال اعلان حذف کنید
- GitHub issues: https://abrova.ir/docs/trace/github از خطاهای ابرووا تریس خودکار issue در GitHub بسازید. هر خطای جدید یک issue با جزئیات خطا و stack trace کامل می‌سازد.
  • یک حساب GitHub
  • یک مخزن GitHub برای پیگیری issueها
  • یک GitHub Personal Access Token
GitHub سرویسی بیرون از ایران است و ممکن است از برخی شبکه‌های داخل ایران در دسترس نباشد. برای راه‌اندازی و خواندن هشدارها به اتصالی نیاز دارید که به GitHub برسد، و issueها فقط وقتی ساخته می‌شوند که github.com و api.github.com پاسخ دهند؛ ارسال‌های ناموفق در لاگ ارسال یکپارچگی نمایش داده می‌شوند. اگر این برای تیم شما مشکل است، به‌جای آن یا در کنارش از یکپارچگی [ایمیل](/docs/trace/email) یا [Webhook](/docs/trace/webhooks) استفاده کنید. این‌که پروژه‌ی شما از کدام کانال‌ها می‌تواند استفاده کند، در صفحه‌ی **ابرووا تریس ← یکپارچه‌سازی‌ها** در کنسول ابرووا نمایش داده می‌شود.

ساختن GitHub Personal Access Token

1. به **GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)** بروید 2. روی **«Generate new token»** ← **«Generate new token (classic)»** کلیک کنید 3. توکن را پیکربندی کنید: - **Note:** «AbrovaTrace Integration» - **Expiration:** ۹۰ روز توصیه می‌شود - **Scopes:** گزینه‌ی `repo` را انتخاب کنید (کنترل کامل مخزن‌های خصوصی) 4. روی **«Generate token»** کلیک کنید و توکن را همان لحظه کپی کنید بعد از ترک صفحه دیگر نمی‌توانید توکن را ببینید. آن را جای امنی ذخیره کنید.

پیکربندی در ابرووا تریس

1. در کنسول ابرووا، **ابرووا تریس ← یکپارچه‌سازی‌ها** را باز کنید (یکپارچگی‌ها متعلق به پروژه‌ی انتخاب‌شده‌اند) 2. روی **«افزودن یکپارچه‌سازی»** کلیک کنید و **GitHub** را انتخاب کنید (یا روی کارت GitHub **«اتصال»** را بزنید) 3. یک **نام** وارد کنید و فیلدهای پیکربندی را پر کنید 4. روی **«ذخیرهٔ کانال»** کلیک کنید، یا **«ذخیره و ارسال تست»** را بزنید تا ذخیره و بررسی شود #### بعد از ذخیره مدیریت یکپارچگی‌ها (افزودن، ویرایش، روشن و خاموش کردن، حذف، تست) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند آن‌ها و لاگ ارسالشان را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. توکن دسترسی شخصی بعد از ذخیره دیگر هرگز نمایش داده نمی‌شود: فرم آن را «تنظیم‌شده» نشان می‌دهد. دست نزنید تا توکن ذخیره‌شده بماند، یا **جایگزینی** را بزنید تا توکن تازه‌ای وارد کنید. اگر صاحب یا نام مخزن را تغییر دهید، توکن را هم دوباره وارد کنید؛ توکن ذخیره‌شده هرگز خودبه‌خود برای مخزن دیگری استفاده نمی‌شود. ### چه چیزی ساخته می‌شود وقتی خطای جدیدی شناسایی می‌شود (اولین باری که دیده می‌شود)، ابرووا تریس خودکار یک issue در GitHub با این محتوا می‌سازد: - **عنوان**: `[AbrovaTrace] ` (مثلاً `[AbrovaTrace] TypeError: Cannot read property 'map' of undefined`) - **جزئیات خطا**: پروژه، محیط، زمان اولین دیده شدن و تعداد رخدادها - **Stack trace**: stack کامل از رویداد خطا - **اطلاعات بیشتر**: URL صفحه و user agent تشخیص جهش هم اینجا issue باز می‌کند، و قوانین هشداری که به این کانال وصل می‌کنید، وقتی فعال شوند issueی با عنوان `[AbrovaTrace Alert] ` و برچسب `abrovatrace-alert` باز می‌کنند. ### بهترین روش‌ها ### عیب‌یابی
  • مطمئن شوید توکن دسترسی شخصی درست است
  • مطمئن شوید توکن دسترسی «repo» را دارد
  • مطمئن شوید صاحب و نام مخزن درست‌اند
  • مطمئن شوید مخزن وجود دارد و به آن دسترسی دارید
  • یکپارچگی را با دکمه‌ی «ارسال تست» تست کنید
  • لاگ ارسال یکپارچگی را ببینید: timeout یا خطای اتصال یعنی GitHub در دسترس نبوده است
  • ممکن است توکن منقضی شده باشد؛ توکن تازه بسازید، سپس یکپارچگی را ویرایش کنید و روی توکن «جایگزینی» را بزنید
  • توکن دسترسی کافی ندارد
  • ممکن است به مخزن دسترسی نداشته باشید
  • توکن را با دسترسی «repo» دوباره بسازید
  • صاحب و نام مخزن را در فیلدهای جداگانه وارد کنید
  • «github.com» یا «https://» را ننویسید
  • درست: صاحب myorg، نام abrova-trace
  • نادرست: github.com/myorg/abrova-trace
- Slack: https://abrova.ir/docs/trace/slack اعلان‌های لحظه‌ای خطا را در workspace مربوط به Slack بگیرید. یاد بگیرید چطور webhook بسازید، آن را وصل کنید و اعلان‌ها چه شکلی دارند.
  • ساختن incoming webhook در Slack
  • پیکربندی اعلان‌های Slack در ابرووا تریس
  • محتوای اعلان‌ها
  • تست یکپارچگی Slack
Slack سرویسی بیرون از ایران است و ممکن است از برخی شبکه‌های داخل ایران در دسترس نباشد. برای راه‌اندازی و خواندن هشدارها به اتصالی نیاز دارید که به Slack برسد، و پیام‌ها فقط وقتی می‌رسند که hooks.slack.com پاسخ دهد؛ ارسال‌های ناموفق در لاگ ارسال یکپارچگی نمایش داده می‌شوند. اگر این برای تیم شما مشکل است، به‌جای آن یا در کنارش از یکپارچگی [ایمیل](/docs/trace/email) یا [Webhook](/docs/trace/webhooks) استفاده کنید. این‌که پروژه‌ی شما از کدام کانال‌ها می‌تواند استفاده کند، در صفحه‌ی **ابرووا تریس ← یکپارچه‌سازی‌ها** در کنسول ابرووا نمایش داده می‌شود.

ساختن webhook در Slack

1. به **api.slack.com/apps** بروید 2. روی **«Create New App»** ← **«From scratch»** کلیک کنید 3. نام اپ را **«AbrovaTrace»** بگذارید و workspace خود را انتخاب کنید 4. در نوار کناری به **«Incoming Webhooks»** بروید 5. **«Activate Incoming Webhooks»** را روشن کنید 6. روی **«Add New Webhook to Workspace»** کلیک کنید 7. کانالی را که می‌خواهید اعلان‌ها به آن بروند انتخاب کنید 8. URL مربوط به webhook را کپی کنید URL مربوط به webhook شما این شکلی است: `https://hooks.slack.com/services/...`

پیکربندی در ابرووا تریس

1. در کنسول ابرووا، **ابرووا تریس ← یکپارچه‌سازی‌ها** را باز کنید (یکپارچگی‌ها متعلق به پروژه‌ی انتخاب‌شده‌اند) 2. روی **«افزودن یکپارچه‌سازی»** کلیک کنید و **Slack** را انتخاب کنید (یا روی کارت Slack **«اتصال»** را بزنید) 3. یک **نام** وارد کنید و URL مربوط به webhook را جای‌گذاری کنید 4. روی **«ذخیره و ارسال تست»** کلیک کنید تا ذخیره و بررسی شود (یا **«ذخیرهٔ کانال»**) #### بعد از ذخیره مدیریت یکپارچگی‌ها (افزودن، ویرایش، روشن و خاموش کردن، حذف، تست) نقش **admin** یا **owner** می‌خواهد؛ همه‌ی نقش‌ها می‌توانند آن‌ها و لاگ ارسالشان را ببینند. [نقش‌ها و دسترسی‌ها](/docs/trace/error-dashboard#roles) را ببینید. URL مربوط به webhook بعد از ذخیره دیگر هرگز نمایش داده نمی‌شود: فرم آن را «تنظیم‌شده» نشان می‌دهد و فقط `https://hooks.slack.com/…` دیده می‌شود. دست نزنید تا URL ذخیره‌شده بماند، یا **جایگزینی** را بزنید تا URL تازه‌ای جای‌گذاری کنید. فقط آدرس‌های webhook خود Slack (`https://hooks.slack.com/...`) پذیرفته می‌شوند. ### قالب اعلان پیام‌های Slack از ابرووا تریس شامل این‌هاست: - **سرتیتر**: نام کاربری ربات AbrovaTrace همراه با آیکون ⚠️ - **عنوان هشدار**: `New Error: `، با لینک به خطا - **متادیتا**: پروژه، محیط، زمان اولین دیده شدن و تعداد رخدادها پیام‌های خطای جدید اولین باری که خطا دیده می‌شود فرستاده می‌شوند. تشخیص جهش هم اینجا پیام می‌گذارد، و قوانین هشدار و مانیتورهای cron هم وقتی این کانال را برایشان انتخاب کنید. ### بهترین روش‌ها ### عیب‌یابی
  • مطمئن شوید URL مربوط به webhook درست است؛ باید با https://hooks.slack.com/ شروع شود
  • مطمئن شوید یکپارچگی در ابرووا تریس ← یکپارچه‌سازی‌ها روشن است
  • مطمئن شوید اپ Slack در workspace شما نصب است
  • دوباره دکمه‌ی «ارسال تست» را امتحان کنید
  • لاگ ارسال یکپارچگی را ببینید: timeout یا خطای اتصال یعنی Slack در دسترس نبوده است
  • URL مربوط به webhook کانال را تعیین می‌کند
  • برای کانال درست یک webhook تازه بسازید
  • یکپارچگی را در ابرووا تریس ← یکپارچه‌سازی‌ها ویرایش کنید و روی URL مربوط به webhook «جایگزینی» را بزنید
  • Slack برای incoming webhookها محدودیت نرخ دارد
  • از گروه‌بندی خطاها برای کم کردن تعداد اعلان‌ها استفاده کنید
  • با قوانین نادیده‌گیری، خطاهای پرسروصدا را حذف کنید
- معرفی و راه‌اندازی: https://abrova.ir/docs/trace/self-hosted نسخه‌ی self-hosted ابرووا تریس هنوز در دسترس نیست. این صفحه توضیح می‌دهد چطور کار خواهد کرد. برای این‌که هنگام انتشار باخبر شوید، یا برای گفت‌وگو درباره‌ی اجرای ابرووا تریس روی سرورهای خودتان، [با ما تماس بگیرید](/contact). ### نسخه‌ی Self-Hosted: معرفی و راه‌اندازی ابرووا تریس را روی زیرساخت خودتان اجرا کنید. همان امکانات نسخه‌ی ابری، و کنترل داده‌ها دست شماست. نسخه‌ی self-hosted دقیقاً از همان SDKها و داشبورد نسخه‌ی ابری استفاده می‌کند. تنها تفاوت جای ذخیره‌ی داده‌هاست.

چه چیزی می‌گیرید

- ردیابی خطا، لاگ‌ها، APM، breadcrumbها و تشخیص کرش - داشبوردی با همه‌ی امکانات نسخه‌ی ابری - ۶ SDK: Flutter، Next.js، Node.js، Android، iOS، Laravel - source map و symbolication با dSYM - قوانین هشدار، مانیتورهای cron و یکپارچگی‌ها (ایمیل، Webhook و غیره) - داده‌ها روی سرور شما می‌مانند

پیش‌نیازها

- Docker و Docker Compose - ۵۱۲ مگابایت RAM (کنسول) به‌علاوه‌ی ۱ گیگابایت RAM (تریس) - روی یک VPS کوچک یا سیستم محلی کار می‌کند

نصب کنسول ابرووا

کنسول داشبورد مشترک همه‌ی سرویس‌های self-hosted ابرووا است. به‌صورت یک stack در Docker Compose اجرا می‌شود: داشبورد کنسول، API آن و دیتابیس خودش، همه روی سرور شما. 1. فایل‌های راه‌اندازی self-hosted را دانلود کنید (هنگام انتشار self-hosted منتشر می‌شوند). این فایل‌ها یک `docker-compose.yml` آماده برای کنسول دارند. 2. کنار آن یک فایل `.env` بسازید و secretهای تصادفی و رمز دیتابیس خودتان را تنظیم کنید؛ فایل‌های راه‌اندازی همه‌ی متغیرها را فهرست کرده‌اند. 3. اجرایش کنید: ```bash docker compose up -d ``` `http://localhost:3000` را باز کنید و حساب خود را ثبت کنید (اولین کاربر صاحب سرور می‌شود).

نصب ابرووا تریس

ابرووا تریس به‌صورت stack دوم Compose اجرا می‌شود: Trace API با دیتابیس و cache خودش. 1. از `docker-compose.trace.yml` در همان فایل‌های راه‌اندازی استفاده کنید. 2. یک فایل `.env.trace` بسازید: secretهای تصادفی و رمز دیتابیس خودتان، آدرس Console API (مثلاً `http://host.docker.internal:3004`) و آدرسی که داشبورد روی آن باز می‌شود (مثلاً `http://localhost:3000`). 3. اجرایش کنید: ```bash docker compose -f docker-compose.trace.yml --env-file .env.trace up -d ``` به کنسول (`http://localhost:3000`) بروید و در نوار کناری روی **Abrova Trace** کلیک کنید.

وصل کردن SDK

با افزودن `apiUrl` هر SDK را به Trace API روی سرور خودتان وصل کنید. برای هر ۶ SDK صفحه‌ی [پیکربندی SDK](/docs/trace/self-hosted-sdk) را ببینید.

پورت‌ها

stackها روی سرور شما روی این پورت‌ها گوش می‌دهند: - **3000**: داشبورد کنسول - **3004**: Console Auth API - **3001**: Trace API ابرووا

به‌روزرسانی

```bash # Console docker compose pull && docker compose up -d # Trace docker compose -f docker-compose.trace.yml pull docker compose -f docker-compose.trace.yml up -d ```

لایسنس

self-hosting با یک سطح رایگان شروع می‌شود که کلید لایسنس لازم ندارد. [لایسنس Self-Hosted](/docs/trace/licensing) را ببینید. - پیکربندی SDK: https://abrova.ir/docs/trace/self-hosted-sdk نسخه‌ی self-hosted ابرووا تریس هنوز در دسترس نیست. این صفحه نشان می‌دهد SDKها چطور به سرور خودتان وصل خواهند شد. برای سرویس ابری ابرووا `apiUrl` را نگذارید؛ SDKها به‌طور پیش‌فرض از `https://trace.abrova.ir` استفاده می‌کنند. سؤالی دارید؟ [با ما تماس بگیرید](/contact). ### پیکربندی SDK برای Self-Hosted هر ۶ SDK با نسخه‌ی self-hosted کار می‌کنند. فقط `apiUrl` را به آدرس سرور خودتان اضافه کنید. بقیه‌ی چیزها همان می‌ماند. ### Flutter ### Next.js ```tsx AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', apiUrl: 'http://your-server:3001', // Your self-hosted Trace API environment: process.env.NODE_ENV, release: process.env.NEXT_PUBLIC_APP_VERSION, }); ``` ### Node.js ```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); AbrovaTrace.init({ apiKey: 'ab_live_your_api_key_here', serverSecret: 'ab_srv_your_server_secret_here', apiUrl: 'http://your-server:3001', // Your self-hosted Trace API environment: 'production', release: '1.0.0' }); ``` ### Android (Kotlin) ```kotlin val config = AbrovaTraceConfig.Builder("ab_live_your_api_key") .apiUrl("http://your-server:3001") // Your self-hosted Trace API .environment(if (BuildConfig.DEBUG) "development" else "production") .release(BuildConfig.VERSION_NAME) .build() AbrovaTrace.init(this, config) ``` ### iOS (Swift) ```swift let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_your_api_key") .apiUrl("http://your-server:3001") // Your self-hosted Trace API .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .build() AbrovaTrace.shared.initialize(config: config) ``` ### Laravel این را به `.env` اضافه کنید: ```bash ABROVATRACE_API_URL=http://your-server:3001 ABROVATRACE_API_KEY=ab_live_your_api_key_here ABROVATRACE_SERVER_SECRET=ab_srv_your_secret_here ABROVATRACE_ENVIRONMENT=production ```

نکته‌ها

- به‌جای `your-server:3001` آدرس IP یا دامنه‌ی واقعی سرور خود را بگذارید - برای توسعه‌ی محلی از `http://localhost:3001` استفاده کنید - برای production از HTTPS پشت یک reverse proxy (nginx، Caddy) استفاده کنید - کلید API را از داشبورد کنسول self-hosted بگیرید - کلید اپ (`ab_live_...`) داخل اپ‌های موبایل و وب قرار می‌گیرد؛ کلید سرور (`ab_srv_...`) فقط روی سرورهای شما. [کلید سرور و routeهای سرور](/docs/trace/server-secret) را ببینید - همه‌ی امکانات (breadcrumbها، APM، لاگ‌ها، تشخیص کرش) دقیقاً مثل نسخه‌ی ابری کار می‌کنند - مجوز: https://abrova.ir/docs/trace/licensing نسخه‌ی self-hosted ابرووا تریس هنوز در دسترس نیست و قیمت لایسنس‌ها هم تعیین نشده است. این صفحه توضیح می‌دهد لایسنس چطور کار خواهد کرد. برای گفت‌وگو درباره‌ی لایسنس برای شرکت خود، [با ما تماس بگیرید](/contact). ### لایسنس نسخه‌ی self-hosted از همان ابتدا با یک سطح رایگان کار می‌کند. کلید لایسنس ابرووا سقف‌ها را بالا می‌برد و امکانات بزرگ‌تر را فعال می‌کند. فقط کانتینرهای Docker را اجرا کنید و ردیابی خطا را شروع کنید. بدون ثبت‌نام، بدون کلید لایسنس، بدون پرداخت.

لایسنس چه چیزی را تغییر می‌دهد

| امکان | سطح رایگان | با لایسنس | |------------------------|-----------|----------------| | پروژه‌ها | ۱ | بیشتر، طبق لایسنس شما | | مشکل جدید در ماه | ۵۰ | بیشتر، طبق لایسنس شما | | اعضای تیم | ۲ | بیشتر، طبق لایسنس شما | | ردیابی خطا و لاگ‌ها | بله | بله | | APM و Web Vitals | خیر | بله | | source map / dSYM | خیر | بله | | قوانین هشدار سفارشی | ۱ | بیشتر، طبق لایسنس شما | | مانیتورهای cron | ۱ | بیشتر، طبق لایسنس شما | | یکپارچگی‌ها | ایمیل | کانال‌های بیشتر | | تشخیص جهش | خیر | بله | لایسنس‌ها به تومان پرداخت می‌شوند. برای قیمت و لایسنس شرکت خود [با ما تماس بگیرید](/contact). ### وقتی به سقف می‌رسید چه می‌شود «مشکل جدید در ماه» در جدول بالا یعنی تعداد مشکل‌های جدید در هر ماه. هر خطا اولین باری که دیده می‌شود و مشکل جدیدی می‌سازد شمرده می‌شود. رویدادهای بعدی مشکلی که از قبل وجود دارد شمرده نمی‌شوند. وقتی سقف ماهانه پر شود: - **مشکل‌های جدید ذخیره نمی‌شوند** تا وقتی سقف در شروع ماه بعد تازه شود. - **رویدادهای مشکل‌های موجود همچنان آن‌ها را به‌روز می‌کنند**؛ تعدادها و زمان آخرین دیده شدن به‌روز می‌مانند. - **اپ‌های شما هیچ‌وقت مختل نمی‌شوند.** API همچنان پاسخ موفق (`200`) می‌دهد، با بدنه‌ای که می‌گوید رویداد ذخیره نشده، پس هیچ SDKی دوباره تلاش نمی‌کند، صف نمی‌کند یا خطا لاگ نمی‌کند: ```json { "status": "dropped", "code": "plan_limit_reached", "metric": "errors_monthly", "limit": 50, "used": 50, "resets_at": "2026-11-01T00:00:00.000Z" } ``` (مقادیر `limit` و `used` نمونه‌اند و لایسنس شما را نشان می‌دهند.) مانیتورینگ عملکرد به لایسنسی نیاز دارد که آن را شامل شود. بدون آن، spanها و Web Vitalsی که اپ‌های شما می‌فرستند به همان شکل پاسخ می‌گیرند (`200`، `"status": "dropped"`، `"code": "feature_not_in_plan"`) و ذخیره نمی‌شوند. سقف پروژه، قانون هشدار و مانیتور cron هنگام ساختن اعمال می‌شود: ساختن بیش از سقف رد می‌شود. ### فعال کردن لایسنس 1. [با ما تماس بگیرید](/contact) تا کلید لایسنس شرکت خود را بگیرید 2. کنسول self-hosted خود را باز کنید ← Dashboard ← License 3. کلید را جای‌گذاری کنید و روی **Activate** کلیک کنید ### لایسنس چطور کار می‌کند - کلید لایسنس به حساب شما گره خورده است، نه به یک سرور مشخص - سرور شما هر ۲۴ ساعت لایسنس را بررسی می‌کند - اگر سرور شما به ابرووا دسترسی نداشته باشد، از سقف‌هایی که ذخیره کرده استفاده می‌کند (مهلت ۷ روزه) - وقتی لایسنس تمام شود، ۷ روز فرصت دارید و بعد سرور به سطح رایگان برمی‌گردد - برگشت آرام است؛ داده‌های موجود حفظ می‌شوند و فقط سقف‌ها کم می‌شوند ### وقتی لایسنس تمام می‌شود چه می‌شود - پروژه‌های بیشتر از سقف رایگان **فقط‌خواندنی** می‌شوند (جدیدترین‌ها فعال می‌مانند) - اعضای بیشتر از سقف همچنان می‌توانند وارد شوند اما نمی‌توان آن‌ها را به پروژه‌ها اضافه کرد - امکانات لایسنس‌دار (APM، source map) از کار می‌افتند؛ داده‌های عملکردی که اپ‌های شما می‌فرستند دیگر ذخیره نمی‌شوند - همه‌ی داده‌های موجود حفظ می‌شوند؛ چیزی حذف نمی‌شود - فعال کردن دوباره‌ی لایسنس فوراً همه‌ی امکانات را برمی‌گرداند - راهبردهای مدیریت خطا: https://abrova.ir/docs/trace/error-handling بهترین روش‌ها برای پیاده‌سازی ردیابی مؤثر خطا در اپلیکیشن شما. این ۱۰ راهکار را دنبال کنید تا بیشترین بهره را از ابرووا تریس ببرید. ### ۱. برای گرفتن همه‌ی خطاها از initWithZone() استفاده کنید همه‌ی انواع خطا را می‌گیرد، از جمله exceptionهای Dart، خطاهای async، rejectهای Future و exceptionهای پلتفرم.

✓ توصیه‌شده

```dart void main() async { await AbrovaTrace.initWithZone( AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', captureUncaughtErrors: true, ), () async { WidgetsFlutterBinding.ensureInitialized(); runApp(MyApp()); }, ); } ```

✗ ساده (خطاهای async را از دست می‌دهد)

```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); // This only catches some errors await AbrovaTrace.init( AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', ), ); runApp(MyApp()); } ```
### ۲. همیشه stack trace را بفرستید وقتی exception را دستی ثبت می‌کنید، همیشه پارامتر stack trace را بدهید؛ این context برای debug حیاتی است. ```dart try { await riskyOperation(); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, // Always include this! message: 'Failed to complete operation', ); } ``` ### ۳. context معنادار اضافه کنید با پارامتر `extra` داده‌های مرتبط را بفرستید تا شرایط رخ دادن خطا را بفهمید. ```dart try { await processPayment(orderId, amount); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Payment processing failed', extra: { 'order_id': orderId, 'amount': amount, 'payment_method': paymentMethod, 'user_tier': userTier, }, ); } ``` داده‌هایی بفرستید که به بازتولید خطا کمک کنند، اما از اطلاعات حساس مثل رمز عبور یا شماره‌ی کارت بانکی پرهیز کنید. ### ۴. خطاها را با breadcrumbها همراه کنید کارهای کاربر را تا لحظه‌ی خطا ثبت کنید تا یک خط زمانی کامل داشته باشید. ```dart // Track user actions AbrovaTrace.addBreadcrumb( 'User opened checkout screen', type: BreadcrumbType.navigation, ); AbrovaTrace.addBreadcrumb( 'User selected credit card payment', type: BreadcrumbType.user, data: {'payment_type': 'credit_card'}, ); try { await submitOrder(); } catch (e, stackTrace) { // Error will include all breadcrumbs above await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Order submission failed', ); } ``` ### ۵. تشخیص کرش ثبت کرش نیتیو روی iOS و Android خودکار است: SDK فلاتر SDKهای نیتیو ابرووا تریس را همراه خود دارد و آن‌ها را هنگام `init()` / `initWithZone()` راه‌اندازی می‌کند. کد اضافه‌ای لازم نیست. نحوه‌ی کار: روی iOS، کتابخانه‌ی PLCrashReporter کرش‌های signal و Mach exception را روی دیسک ذخیره می‌کند؛ روی Android، یک uncaught exception handler به‌همراه `ApplicationExitInfo` (API 30+) کرش‌ها و ANRها را ثبت می‌کنند. گزارش در اجرای بعدی اپ فرستاده می‌شود و به‌صورت یک مشکل نمایش داده می‌شود. دسکتاپ و وب ثبت کرش نیتیو ندارند. برای تشخیص کرش به `AbrovaTrace.close()` نیازی نیست. این متد لاگ‌های در انتظار را می‌فرستد و SDK را می‌بندد، پس فقط وقتی صدایش بزنید که اپ واقعاً در حال بسته شدن است. ### ۶. برای هر محیط پیکربندی جداگانه داشته باشید ```dart void main() async { const environment = String.fromEnvironment( 'ENV', defaultValue: 'development', ); await AbrovaTrace.initWithZone( AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: environment, release: '1.0.0', captureUncaughtErrors: environment == 'production', enabled: environment != 'development', ), () async { WidgetsFlutterBinding.ensureInitialized(); runApp(MyApp()); }, ); } ``` ### ۷. وضعیت خطاها را در داشبورد مدیریت کنید - **باز:** خطاهای جدیدی که باید بررسی شوند - **در حال بررسی:** خطاهایی که الان رویشان کار می‌کنید - **حل‌شده:** خطاهای رفع‌شده (اگر دوباره رخ دادند، دوباره بازشان کنید) - **نادیده‌گرفته:** مشکل‌های شناخته‌شده‌ای که تصمیم گرفته‌اید رفعشان نکنید - **بایگانی‌شده:** خطاهای قدیمی که می‌خواهید نگه دارید اما در فهرست فعال نبینید ### ۸. خطاهای async را درست مدیریت کنید ```dart // Catching Future errors Future loadData() async { try { final data = await fetchFromAPI(); processData(data); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Failed to load data', ); } } // Handling Stream errors void listenToStream() { myStream.listen( (data) => processData(data), onError: (e, stackTrace) async { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Stream error occurred', ); }, ); } ``` ### ۹. بیش از حد گزارش نکنید
گزارش نکنید
  • خطاهای اعتبارسنجی مورد انتظار
  • لغو کردن کاربر
  • timeoutهای شبکه در حالت آفلاین
  • رد شدن مجوزها
گزارش کنید
  • exceptionهای غیرمنتظره (null reference، index out of bounds)
  • خطاهای API (خطای 500 سرور، پاسخ‌های بدشکل)
  • خراب شدن داده‌ها
  • کرش‌های نیتیو
### ۱۰. ردیابی خطا را تست کنید پیش از deploy به production، مطمئن شوید ردیابی خطا کار می‌کند (رویداد در **ابرووا تریس ← مشکلات** دیده می‌شود): ```dart // Add a test button in debug builds if (kDebugMode) { ElevatedButton( onPressed: () async { try { throw Exception('Test error from debug menu'); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Test error', ); } }, child: Text('Test Error Tracking'), ); } ``` SDK خطای یکسانی (با همان پیام و پلتفرم) را که ظرف یک دقیقه دوباره فرستاده شود کنار می‌گذارد، پس اگر چند بار پشت سر هم تست می‌کنید پیام را تغییر دهید. ### چک‌لیست خلاصه
  • ✓برای گرفتن همه‌ی خطاها از initWithZone() استفاده کنید
  • ✓در ثبت دستی همیشه stack trace را بفرستید
  • ✓با پارامتر extra context معنادار اضافه کنید
  • ✓خطاها را با breadcrumbها همراه کنید تا مسیر کاربر را ببینید
  • ✓روی build واقعی iOS/Android تست کنید تا کرش‌های نیتیو ثبت شوند
  • ✓برای هر محیط پیکربندی جداگانه داشته باشید
  • ✓وضعیت خطاها را در داشبورد به‌خوبی مدیریت کنید
  • ✓خطاهای async را درست مدیریت کنید
  • ✓فقط خطاهایی را گزارش کنید که قابل‌اقدام‌اند
  • ✓پیش از production ردیابی خطا را تست کنید
## Abrova Auth - معرفی و شروع سریع: https://abrova.ir/docs/auth/quick-start با Abrova Auth کاربران اپ شما بدون سرورِ خودتان وارد می‌شوند. اپ با کلید API عمومی‌اش SDK را صدا می‌زند؛ ابرووا اطلاعات ورود را بررسی می‌کند، نشست را نگه می‌دارد و یک **توکن کاربر** به اپ می‌دهد. این همان توکن کاربر ابرووا است: ابرووا تریس از همین امروز می‌تواند کاربرِ واردشده را به خطاها وصل کند، و Sync، Chat، Push، Storage و A/B Testing هم وقتی راه بیفتند همین توکن را می‌پذیرند؛ به این ترتیب قوانین امنیتی‌شان می‌دانند کاربر کیست (`auth.uid`) بی‌آنکه سرور شما چیزی بسازد. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف و کد تلگرام را در اولویت بگذارید. ورود با Apple و Google به اتصال به Apple یا Google (و در Android به Google Play services) نیاز دارد که بعضی کاربران در ایران ندارند. آن‌ها را ارائه کنید، اما تنها راه ورود نباشند. از دستیار هوش مصنوعی استفاده می‌کنید؟ این صفحه را با **Copy page for AI** به آن بدهید، یا همه‌ی مستندات را به‌صورت متن ساده از [/docs/llms.txt](/docs/llms.txt) بگیرید. [دستیارهای هوش مصنوعی](/docs/auth/ai-assistants) را ببینید. ### SDKهای موجود همه‌ی پکیج‌ها از Abrova Center (`center.abrova.ir`) می‌آیند؛ SDK iOS با Swift Package Manager از GitHub نصب می‌شود. | پلتفرم | پکیج | مستندات | |---|---|---| | Flutter | `abrova_auth` | [SDK Flutter](/docs/auth/flutter) | | iOS | `AbrovaAuth` (Swift Package Manager) | [SDK iOS](/docs/auth/ios) | | Android | `ir.abrova:abrova-auth` | [SDK Android](/docs/auth/android) | | Web | `@abrova/auth-web` | [SDK Web](/docs/auth/web) | | React Native | `@abrova/auth-react-native` | [SDK React Native](/docs/auth/react-native) | هر پنج SDK همان فراخوانی‌ها را با نام‌گذاری مخصوص هر پلتفرم دارند. Abrova Auth برای اپ‌هاست: SDK سرور ندارد و نیازی هم به آن نیست.

Abrova Auth را روشن کنید

1. در [کنسول ابرووا](https://console.abrova.ir)، **تنظیمات › محصولات** پروژه را باز کنید و **Abrova Auth** را روشن کنید. 2. **Abrova Auth › روش‌های ورود** را باز کنید و روش‌هایی را که اپ شما ارائه می‌دهد روشن کنید: ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف، کد تلگرام، Apple و Google. همه‌ی روش‌ها تا وقتی روشنشان نکنید خاموش‌اند. 3. برای Apple و Google، شناسه‌های اپ را در همان صفحه اضافه کنید. برای لینک‌های ایمیلی، **آدرس‌های لینک** را در **Abrova Auth › تنظیمات** اضافه کنید. برای تلگرام، ربات‌تان را وصل کنید. صفحه‌ی هر روش می‌گوید به چه چیزی نیاز دارد.

کلیدها

Abrova Auth فقط به **کلید API عمومی** پروژه (`ab_live_...`) نیاز دارد که در **Abrova Auth › راه‌اندازی** نمایش داده می‌شود. قرار دادنش در اپ امن است: فقط پروژه را مشخص می‌کند و نمی‌تواند به جای هیچ کاربری عمل کند. | اعتبارنامه | کجا نگه‌داری می‌شود | چه چیزی را ثابت می‌کند | |---|---|---| | **کلید API** (`ab_live_…`) | اپ شما، عمومی | کدام پروژه | | **access token** (یک ساعت) | حافظه‌ی دستگاه | کدام کاربر وارد شده است | | **refresh token** | حافظه‌ی امن دستگاه | اینکه این دستگاه اجازه دارد access token جدید بگیرد | SDK هر دو توکن را برای شما نگه می‌دارد. هیچ‌وقت لازم نیست خودتان با refresh token کار کنید. Abrova Auth از کلید سرور (`ab_srv_...`) استفاده نمی‌کند. آن را فقط روی سرور خودتان نگه دارید، بیرون از اپ‌ها و bundleهای مرورگر.

نصب و ورود

کوتاه‌ترین مسیر، کاربر ناشناس است: بدون صفحه، با یک فراخوانی. اول **ناشناس** را در کنسول روشن کنید. پکیج را با رجیستری ابرووا به‌عنوان منبع اضافه کنید: {`dependencies: abrova_auth: hosted: https://center.abrova.ir/api/packages/abrova/pub version: ^0.1.1`} بعد `flutter pub get` را اجرا کنید و وارد شوید: ```dart import 'package:abrova_auth/abrova_auth.dart'; final auth = await AbrovaAuth.initialize('ab_live_your_api_key'); auth.authStateChanges.listen((user) { // null when signed out }); if (auth.currentUser == null) { await auth.signInAnonymously(); } print(auth.currentUser?.id); // ru_... ``` کاربر از اولین اجرا یک شناسه‌ی پایدار و یک توکن کاربر می‌گیرد. بعداً یک روش ورود را به همان کاربر وصل کنید — داده‌هایش مال خودش می‌ماند: ```dart await auth.linkPassword(email: 'sara@example.com', password: password); ``` روی وب هم همین‌طور است، بعد از اینکه scope `@abrova` را در `.npmrc` به Abrova Center ارجاع دادید: {`@abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/`} ```typescript import { createAbrovaAuth } from '@abrova/auth-web'; const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key' }); await auth.ready(); if (!auth.isSignedIn) await auth.signInAnonymously(); console.log(auth.currentUserId); // ru_... ``` صفحه‌ی هر پلتفرم راه‌اندازی کامل را دارد: [Flutter](/docs/auth/flutter)، [iOS](/docs/auth/ios)، [Android](/docs/auth/android)، [Web](/docs/auth/web)، [React Native](/docs/auth/react-native).

استفاده از کاربر در محصولات دیگر ابرووا

امروز شناسه‌ی کاربر را به ابرووا تریس بدهید تا خطاها نشان دهند برای کدام کاربر رخ داده‌اند: ```dart AbrovaTrace.setUserId(auth.currentUser!.id); ``` Abrova Sync، Chat، Push، Storage و A/B Testing به‌زودی می‌آیند. وقتی راه بیفتند، token provider مربوط به SDK Auth را مثل نمونه‌ی زیر به آن‌ها می‌دهید؛ فعلاً قابل استفاده نیستند. SDKهای دیگر ابرووا هر وقت به توکن نیاز داشته باشند آن را از token provider می‌خواهند، و SDK Auth پیش از انقضا تمدیدش می‌کند. ```dart await AbrovaSync.init(AbrovaSyncConfig( apiKey: 'ab_live_your_api_key', tokenProvider: AbrovaAuth.instance.tokenProvider, )); ``` در این حالت قوانین امنیتی Sync کاربرِ واردشده را به‌صورت `auth.uid` می‌بینند. برای همه‌ی محصولات و پلتفرم‌ها [استفاده با محصولات دیگر ابرووا](/docs/auth/other-products) را ببینید. ### چطور کار می‌کند ### قیمت‌گذاری هزینه‌ی Abrova Auth بر اساس **کاربر فعال ماهانه** است: هر کاربری که در یک ماه تقویمی وارد شود یا نشستش را تمدید کند، در آن ماه یک بار شمرده می‌شود، هر چقدر هم از اپ استفاده کند. پلن وجود ندارد: برای مصرفِ بیشتر از سهمیه‌ی رایگان، از کیف پول تومانیِ پیش‌پرداخت هزینه می‌کنید. | | رایگان در هر ماه | بیشتر از آن | |---|---|---| | کاربر فعال ماهانه | ۱٬۰۰۰ | ۱۰٬۰۰۰ تومان برای هر ۱٬۰۰۰، هزینه از کیف پول | | کاربر ناشناس جدید در روز | ۲۰۰ | سقف روزانه | | روش‌های ورود و گزارش فعالیت | همه شامل است | شامل است | وقتی کیف پول خالی باشد و ۱٬۰۰۰ کاربر رایگان هم مصرف شده باشد، کاربرانِ جدیدِ آن ماه منتظر می‌مانند (`MAU_LIMIT`)؛ کاربرانی که قبلاً شمرده شده‌اند همچنان وارد می‌شوند. [کاربر فعال ماهانه و سقف‌ها](/docs/auth/mau) و [صفحه‌ی قیمت‌ها](/pricing) را ببینید. برای کاربران فعال روزانه و کاربران بازگشتی، [آمار استفاده](/docs/auth/usage-insights) را روشن کنید. ### اشتباه‌های رایج - **روش ورود روشن نیست.** هر روش ورود خاموش شروع می‌شود. فراخوانیِ روشی که خاموش است با `METHOD_DISABLED` شکست می‌خورد. `getMethods()` را صدا بزنید و فقط دکمه‌هایی را نشان دهید که برمی‌گرداند. - **Abrova Auth برای پروژه روشن نیست.** تا وقتی آن را در **تنظیمات › محصولات** روشن نکنید، فراخوانی‌ها با `PRODUCT_DISABLED` شکست می‌خورند. - **فقط Apple یا Google ارائه شده است.** بعضی کاربران در ایران به Apple یا Google دسترسی ندارند. ناشناس، ایمیل، لینک یک‌بارمصرف یا کد تلگرام را هم ارائه کنید. - **Apple یا Google می‌گوید ID token نامعتبر است.** bundle ID، Services ID یا Google client ID شما در کنسول نیست، یا nonce‌ای که به ارائه‌دهنده دادید SHA-256 همان nonce خامی نیست که به ابرووا دادید. [ورود با Apple](/docs/auth/apple) و [ورود با Google](/docs/auth/google) را ببینید. - **لینک‌های ایمیلی رد می‌شوند.** `redirectUrl` باید با یکی از آدرس‌های لینک در **Abrova Auth › تنظیمات** شروع شود. - **ورود دوباره در هر بار اجرا.** SDK نشست ذخیره‌شده را هنگام اجرا بازیابی می‌کند. پیش از ورود دوباره بررسی کنید کاربر فعلی وجود دارد یا نه، وگرنه هر اجرا یک کاربر ناشناس جدید می‌سازد. - **استفاده از `currentUser` پیش از بازیابی نشست.** روی وب و React Native، بعد از reload منتظر `ready()` (یا اولین callback وضعیت ورود) بمانید. - SDK فلاتر: https://abrova.ir/docs/auth/flutter این صفحه `abrova_auth` نسخه‌ی 0.1.1 را توضیح می‌دهد. Flutter 3.13 یا بالاتر. Dart 3.0 یا بالاتر. برای نشست از `flutter_secure_storage` استفاده می‌کند. از دستیار هوش مصنوعی استفاده می‌کنید؟ این صفحه را با **Copy page for AI** به آن بدهید، یا همه‌ی مستندات را به‌صورت متن ساده از [/docs/llms.txt](/docs/llms.txt) بگیرید. [دستیارهای هوش مصنوعی](/docs/auth/ai-assistants) را ببینید.

نصب

پکیج‌ها از Abrova Center می‌آیند، پس محدودیت‌های دسترسی به pub.dev مشکلی ایجاد نمی‌کند. پکیج را با رجیستری ابرووا به‌عنوان منبع `hosted` در `pubspec.yaml` اضافه کنید: {`dependencies: abrova_auth: hosted: https://center.abrova.ir/api/packages/abrova/pub version: ^0.1.1`} سپس نصبش کنید: ```bash flutter pub get ``` در کنسول ابرووا، **Abrova Auth** را برای پروژه (**تنظیمات › محصولات**) و روش‌های ورود دلخواه را (**Abrova Auth › روش‌های ورود**) روشن کنید.

مقداردهی اولیه

`AbrovaAuth.initialize` را یک بار، پیش از `runApp`، با کلید API عمومی (`ab_live_...`) صدا بزنید. این تابع نشست ذخیره‌شده روی دستگاه را بدون درخواست شبکه بازیابی می‌کند، پس `currentUser` بلافاصله آماده است. ```dart import 'package:flutter/material.dart'; import 'package:abrova_auth/abrova_auth.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await AbrovaAuth.initialize('ab_live_your_api_key'); runApp(const MyApp()); } ``` سپس هر جا خواستید از `AbrovaAuth.instance` استفاده کنید.

ورود

فقط روش‌هایی را نشان دهید که پروژه‌تان روشن کرده، و فقط وقتی کسی هنوز وارد نشده ورود را انجام دهید: ```dart final auth = AbrovaAuth.instance; final methods = await auth.getMethods(); // methods.anonymous, methods.apple, methods.google, methods.password, methods.magicLink, methods.telegram if (auth.currentUser == null && methods.anonymous) { await auth.signInAnonymously(); } print(auth.currentUser!.id); // ru_... ``` اگر از قبل یک کاربر ناشناس روی این دستگاه وارد شده باشد، `signInAnonymously()` همان کاربر فعلی را برمی‌گرداند.

استفاده از کاربر با SDKهای دیگر ابرووا

Abrova Sync، Chat، Push، Storage و A/B Testing به‌زودی می‌آیند. فراخوانی‌های زیر نشان می‌دهند کاربر Auth را چطور خواهند گرفت؛ امروز کاربر را با `AbrovaTrace.setUserId(auth.currentUser!.id)` به ابرووا تریس وصل کنید. token provider را بدهید. SDK دیگر هر وقت به توکن نیاز داشت آن را از provider می‌خواهد؛ Abrova Auth توکن را پیش از انقضا تمدید می‌کند و درخواست‌کننده‌های هم‌زمان در یک تمدید شریک می‌شوند. ```dart await AbrovaSync.init(AbrovaSyncConfig( apiKey: 'ab_live_your_api_key', tokenProvider: AbrovaAuth.instance.tokenProvider, // null while signed out )); ``` Abrova Chat، `AbrovaAuth.instance.chatTokenProvider` را می‌گیرد (به جای برگرداندن null خطا می‌دهد) و `currentUser.id` را به‌عنوان شناسه‌ی کاربر. بعد از ورود یا خروج، متد refresh آن SDK را صدا بزنید، مثلاً `AbrovaSync.refreshUserToken()`. [استفاده با محصولات دیگر ابرووا](/docs/auth/other-products) را ببینید. ### روش‌های ورود هر روش صفحه‌ی خودش را با راه‌اندازی کنسول دارد: [ناشناس](/docs/auth/anonymous)، [Apple](/docs/auth/apple)، [Google](/docs/auth/google)، [ایمیل و رمز عبور](/docs/auth/email-password)، [لینک یک‌بارمصرف](/docs/auth/magic-link)، [تلگرام](/docs/auth/telegram). #### ایمیل و رمز عبور ```dart final result = await auth.signUpWithEmail( email: email, password: password, redirectUrl: 'https://example.com/auth/verify', // optional: sends a verification e-mail ); // result.signedIn is false when the project requires a verified e-mail first. await auth.signInWithEmail(email: email, password: password); await auth.sendPasswordReset(email, redirectUrl: 'https://example.com/auth/reset'); await auth.resetPassword(tokenFromLink, newPassword); // ends every session; does not sign in await auth.verifyEmail(tokenFromLink); await auth.sendEmailVerification(redirectUrl: 'https://example.com/auth/verify'); ``` لینک‌ها `redirectUrl` شما را با `?token=...` باز می‌کنند. این آدرس باید با یکی از آدرس‌های لینک در **Abrova Auth › تنظیمات** شروع شود. #### لینک یک‌بارمصرف ```dart await auth.sendMagicLink(email, redirectUrl: 'myapp://auth/magic'); // When the app is opened with the link: await auth.signInWithMagicLink(Uri.parse(link).queryParameters['token']!); ``` #### تلگرام ```dart final r = await auth.requestTelegramCode('+989121234567'); if (!r.sent && r.botUrl != null) { // First time: open r.botUrl so the user starts the bot and shares the number. } await auth.verifyTelegramCode('+989121234567', code); ``` #### Apple و Google پلاگین بومی ارائه‌دهنده را با nonce **هش‌شده** اجرا کنید و nonce **خام** را به ابرووا بدهید: ```dart final raw = AbrovaAuthNonce.generate(); // sign_in_with_apple final apple = await SignInWithApple.getAppleIDCredential( scopes: [AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName], nonce: AbrovaAuthNonce.sha256Hex(raw), ); await auth.signInWithApple( idToken: apple.identityToken!, rawNonce: raw, authorizationCode: apple.authorizationCode, // lets account deletion revoke the Apple sign-in name: [apple.givenName, apple.familyName].whereType().join(' '), ); // google_sign_in 7 — use a new nonce for each sign-in final rawG = AbrovaAuthNonce.generate(); await GoogleSignIn.instance.initialize( serverClientId: 'xxx.apps.googleusercontent.com', nonce: AbrovaAuthNonce.sha256Hex(rawG), ); final account = await GoogleSignIn.instance.authenticate(); await auth.signInWithGoogle(idToken: account.authentication.idToken!, rawNonce: rawG); ``` اول bundle IDها و Google client IDهایتان را در کنسول اضافه کنید. هر nonce فقط یک بار کار می‌کند. ورود با Google به اتصال به Google، و در Android به Google Play services هم، نیاز دارد؛ بعضی کاربران در ایران هیچ‌کدام را ندارند. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف و کد تلگرام بدون Google کار می‌کنند، پس دست‌کم یکی از آن‌ها را هم ارائه کنید. [ورود با Google](/docs/auth/google) را ببینید. ### ارتقای کاربر ناشناس وصل کردن شناسه‌ی کاربر را حفظ می‌کند، پس داده‌های کاربر مال خودش می‌ماند: ```dart try { await auth.linkGoogle(idToken: idToken, rawNonce: raw); // or linkApple(...), linkPassword(email: ..., password: ...) } on AbrovaProviderAlreadyLinkedException { // This Google account already has a user: offer to sign in with it instead. } ``` [کاربران ناشناس و ارتقا](/docs/auth/upgrading) را ببینید. ### کاربر فعلی ```dart auth.currentUser; // AbrovaAuthUser? — null when signed out auth.isSignedIn; auth.authStateChanges.listen((user) { // Emits the current user first, then on every sign-in, sign-out and profile change. }); final me = await auth.getMe(); // fresh from the server final token = await auth.getToken(); // the access token, if you call your own API with it ``` ', 'روش‌های وصل‌شده، مثلاً apple، password'], ]} /> ### خروج و حذف حساب ```dart await auth.signOut(); // this device; never fails await auth.signOutEverywhere(); // every device await auth.deleteAccount(); // deletes the user, then signs out ``` Apple از اپ‌های دارای ورود می‌خواهد امکان حذف حساب را داخل اپ ارائه کنند. [امنیت و داده‌ها](/docs/auth/security) را ببینید. ### مدیریت خطاها همه‌ی فراخوانی‌ها `AbrovaAuthException` پرتاب می‌کنند: ```dart try { await auth.signInWithEmail(email: email, password: password); } on AbrovaAuthException catch (e) { switch (e.code) { case AbrovaAuthErrorCode.invalidCredentials: showError('Wrong e-mail or password'); case AbrovaAuthErrorCode.rateLimited: showError('Too many attempts. Try again in ${e.retryAfter?.inSeconds ?? 60} s'); case AbrovaAuthErrorCode.networkError: showError('No connection'); default: showError('Sign-in failed'); } } ``` مقدار `code` یکی از `AbrovaAuthErrorCode` است؛ `message` یک راهنمای انگلیسی برای توسعه‌دهنده است، نه برای کاربر. `isNetworkError` و `isSessionEnded` در حالت‌های رایج کمک می‌کنند. خطای شبکه هرگز کاربر را خارج نمی‌کند؛ وقتی نشست پایان می‌یابد (لغوشده، منقضی، حساب غیرفعال)، SDK خارج می‌شود و `authStateChanges` مقدار `null` می‌فرستد. همه‌ی کدها: [کدهای خطا](/docs/auth/error-codes). ### عیب‌یابی

Abrova Auth را در تنظیمات › محصولات و روش را در Abrova Auth › روش‌های ورود روشن کنید. با getMethods() فقط روش‌هایی را نشان دهید که روشن‌اند.

پیش از خواندن currentUser، initialize() را صدا بزنید و فقط وقتی currentUser برابر null است وارد شوید.

بررسی کنید bundle ID، Services ID یا client ID در کنسول باشد، و به ارائه‌دهنده AbrovaAuthNonce.sha256Hex(raw) و به ابرووا مقدار خام را داده باشید.

آن‌ها توکن‌شان را cache می‌کنند. متد refresh آن‌ها (مثلاً AbrovaSync.refreshUserToken()) را از listener مربوط به authStateChanges صدا بزنید.

- SDK آی‌اواس: https://abrova.ir/docs/auth/ios این صفحه `AbrovaAuth` نسخه‌ی 0.1.1 را توضیح می‌دهد. iOS 13 یا بالاتر، یا macOS 12 یا بالاتر. Swift 5.7 یا بالاتر. از دستیار هوش مصنوعی استفاده می‌کنید؟ این صفحه را با **Copy page for AI** به آن بدهید، یا همه‌ی مستندات را به‌صورت متن ساده از [/docs/llms.txt](/docs/llms.txt) بگیرید. [دستیارهای هوش مصنوعی](/docs/auth/ai-assistants) را ببینید.

نصب

با Swift Package Manager نصب کنید. در Xcode: **File → Add Packages** → آدرس مخزن را وارد کنید: ```text https://github.com/Abrova-co/abrova-auth-ios-sdk ``` یا آن را به `Package.swift` اضافه کنید: ```swift dependencies: [ .package(url: "https://github.com/Abrova-co/abrova-auth-ios-sdk.git", from: "0.1.1") ] ``` سپس `AbrovaAuth` را به وابستگی‌های target اضافه کنید. در کنسول ابرووا، **Abrova Auth** را برای پروژه (**تنظیمات › محصولات**) و روش‌های ورود دلخواه را (**Abrova Auth › روش‌های ورود**) روشن کنید.

مقداردهی اولیه

SDK را یک بار هنگام شروع اپ، پیش از هر فراخوانی دیگر، با کلید API عمومی (`ab_live_...`) پیکربندی کنید. نشستی که در اجرای قبلی ذخیره شده بازیابی می‌شود. ```swift import AbrovaAuth @main struct MyApp: App { init() { AbrovaAuth.shared.configure(apiKey: "ab_live_your_api_key") } var body: some Scene { WindowGroup { ContentView() } } } ``` Keychain بعد از حذف اپ هم باقی می‌ماند، به همین دلیل SDK نشستی را که از نصب قبلی مانده فراموش می‌کند: نصب تازه در حالت خارج‌شده شروع می‌شود.

ورود

```swift let auth = AbrovaAuth.shared let methods = try await auth.getMethods() if auth.currentUserId == nil && methods.isEnabled("anonymous") { try await auth.signInAnonymously() } print(auth.currentUserId!) // ru_... ``` فقط دکمه‌های روش‌هایی را نشان دهید که `methods.isEnabled(...)` برایشان true است: `anonymous`، `apple`، `google`، `password`، `magicLink`، `telegram`.

استفاده از کاربر با SDKهای دیگر ابرووا

Abrova Sync، Chat، Push، Storage و A/B Testing به‌زودی می‌آیند. فراخوانی‌های زیر نشان می‌دهند کاربر Auth را چطور خواهند گرفت؛ امروز کاربر را با `AbrovaTrace.shared.setUserId(auth.currentUserId)` به ابرووا تریس وصل کنید. ```swift // Abrova Sync sync.userTokens.provider = auth.tokenProvider // Abrova Push AbrovaPushConfig(apiKey: key, tokenProvider: auth.tokenProvider) // Abrova Chat: create the client after sign-in AbrovaChatConfig(apiKey: key, userId: auth.currentUserId!, tokenProvider: auth.strictTokenProvider) ``` وقتی کسی وارد نشده، `tokenProvider` مقدار `nil` برمی‌گرداند؛ `strictTokenProvider` خطا می‌دهد. هر دو توکنی برمی‌گردانند که پیش از انقضا تمدید شده است. بعد از ورود یا خروج، متد refresh آن SDK را صدا بزنید (مثلاً `sync.refreshUserToken()`). Abrova A/B Testing و موارد دیگر: [استفاده با محصولات دیگر ابرووا](/docs/auth/other-products). ### روش‌های ورود هر روش صفحه‌ی خودش را با راه‌اندازی کنسول دارد: [ناشناس](/docs/auth/anonymous)، [Apple](/docs/auth/apple)، [Google](/docs/auth/google)، [ایمیل و رمز عبور](/docs/auth/email-password)، [لینک یک‌بارمصرف](/docs/auth/magic-link)، [تلگرام](/docs/auth/telegram). #### ورود با Apple قابلیت **Sign in with Apple** را به اپ و bundle ID را در **Abrova Auth › روش‌های ورود › Apple** اضافه کنید. سپس: ```swift try await AbrovaAuth.shared.signInWithAppleNative() ``` SDK صفحه‌ی Apple را با یک nonce اجرا می‌کند و کد authorization (تا حذف حساب بتواند ورود با Apple را لغو کند) و نامی را که Apple در اولین ورود می‌دهد ارسال می‌کند. وقتی کاربر صفحه را ببندد، خطای `.canceled` می‌دهد. اگر درخواست Apple را خودتان اجرا می‌کنید: ```swift let rawNonce = AbrovaNonce.generate() request.nonce = AbrovaNonce.sha256(rawNonce) // to Apple // ... when Apple answers: try await auth.signInWithApple(idToken: idToken, rawNonce: rawNonce, authorizationCode: code, fullName: name) ``` #### ورود با Google ID token مربوط به Google را با `AbrovaNonce.sha256(rawNonce)` به‌عنوان nonce درخواست کنید، سپس: ```swift try await auth.signInWithGoogle(idToken: idToken, rawNonce: rawNonce) ``` ورود با Google به اتصال به Google نیاز دارد که بعضی کاربران در ایران ندارند. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف و کد تلگرام بدون Google کار می‌کنند، پس دست‌کم یکی از آن‌ها را هم ارائه کنید. [ورود با Google](/docs/auth/google) را ببینید. #### ایمیل و رمز عبور ```swift let result = try await auth.signUp(email: email, password: password, redirectURL: "https://example.com/auth/verify") // result.signedIn is false when the project requires a verified e-mail first. try await auth.signIn(email: email, password: password) try await auth.sendPasswordReset(email: email, redirectURL: "https://example.com/auth/reset") try await auth.resetPassword(token: token, newPassword: newPassword) // ends every session try await auth.verifyEmail(token: token) try await auth.sendEmailVerification(redirectURL: "https://example.com/auth/verify") ``` `AbrovaAuth.token(in: url)` مقدار `token` را از لینکی که اپ با آن باز شده می‌خواند. #### لینک یک‌بارمصرف ```swift try await auth.sendMagicLink(email: email, redirectURL: "myapp://auth/magic") // When the app is opened with the link: try await auth.signInWithMagicLink(url: openedURL) ``` #### تلگرام ```swift let code = try await auth.requestTelegramCode(phone: "+989121234567") if !code.sent, let url = code.botURL { await UIApplication.shared.open(url) } try await auth.verifyTelegramCode(phone: "+989121234567", code: "123456") ``` ### ارتقای کاربر ناشناس وصل کردن شناسه‌ی کاربر را حفظ می‌کند، پس هر چه ساخته مال خودش می‌ماند: ```swift do { try await auth.signInWithAppleNative(link: true) // or linkGoogle(idToken:rawNonce:), linkApple(...), linkPassword(email:password:) } catch let error as AbrovaAuthError where error.code == .providerAlreadyLinked { // This Apple ID already belongs to another user. } ``` ### کاربر فعلی ```swift auth.currentUserId // nil when signed out auth.isSignedIn let user = try await auth.me() // AbrovaUser: id, isAnonymous, emailVerified, createdAt, providers auth.currentUser // the profile last loaded by me() let listener = auth.addAuthStateListener { userId in // On the main queue: now, then on every sign-in, sign-out and session end } listener.remove() let token = try await auth.getAccessToken() // if you call your own API with it ``` ### خروج و حذف حساب ```swift await auth.signOut() // this device; never fails try await auth.signOutEverywhere() // every device try await auth.deleteAccount() // required by the App Store for apps with sign-in ``` برای اینکه حذف چه چیزهایی را پاک می‌کند، [امنیت و داده‌ها](/docs/auth/security) را ببینید. ### مدیریت خطاها همه‌ی خطاها از نوع `AbrovaAuthError` هستند. بر اساس `code` تصمیم بگیرید: ```swift do { try await auth.signIn(email: email, password: password) } catch let error as AbrovaAuthError { switch error.code { case .invalidCredentials: showError("Wrong e-mail or password") case .rateLimited: showError("Try again in \(error.retryAfterSec ?? 60) s") case .network: showError("No connection") default: showError("Sign-in failed") } } ``` `message` یک راهنمای انگلیسی برای توسعه‌دهنده است. SDK علاوه بر کدهای سرور از `.network`، `.canceled` (کاربر صفحه‌ی Apple را بست)، `.notSignedIn` و `.sdkNotConfigured` (اول `configure` را صدا بزنید) استفاده می‌کند. خطای شبکه هرگز کاربر را خارج نمی‌کند. همه‌ی کدها: [کدهای خطا](/docs/auth/error-codes). ### عیب‌یابی

AbrovaAuth.shared.configure(apiKey:) را هنگام شروع اپ، پیش از هر فراخوانی دیگر، صدا بزنید.

bundle ID اپ را در Abrova Auth › روش‌های ورود › Apple، و قابلیت Sign in with Apple را به target اضافه کنید.

Abrova Auth را در تنظیمات › محصولات و روش را در Abrova Auth › روش‌های ورود روشن کنید.

- SDK اندروید: https://abrova.ir/docs/auth/android این صفحه `ir.abrova:abrova-auth` نسخه‌ی 0.1.1 را توضیح می‌دهد. Android API 23 (Android 6.0) یا بالاتر. Kotlin coroutines. از دستیار هوش مصنوعی استفاده می‌کنید؟ این صفحه را با **Copy page for AI** به آن بدهید، یا همه‌ی مستندات را به‌صورت متن ساده از [/docs/llms.txt](/docs/llms.txt) بگیرید. [دستیارهای هوش مصنوعی](/docs/auth/ai-assistants) را ببینید.

نصب

SDK از Abrova Center می‌آید. اول مخزن Maven ابرووا را در `settings.gradle.kts` اضافه کنید: {`dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://center.abrova.ir/api/packages/abrova/maven") } } }`} سپس وابستگی را در `build.gradle.kts` اپ اضافه کنید: ```kotlin dependencies { implementation("ir.abrova:abrova-auth:0.1.1") } ``` در کنسول ابرووا، **Abrova Auth** را برای پروژه (**تنظیمات › محصولات**) و روش‌های ورود دلخواه را (**Abrova Auth › روش‌های ورود**) روشن کنید.

مقداردهی اولیه

SDK را یک بار، در `Application.onCreate`، با کلید API عمومی (`ab_live_...`) پیکربندی کنید. نشستی که در اجرای قبلی ذخیره شده بازیابی می‌شود. ```kotlin import ir.abrova.auth.AbrovaAuth class MyApp : Application() { override fun onCreate() { super.onCreate() AbrovaAuth.shared.configure(this, "ab_live_your_api_key") } } ``` همه‌ی فراخوانی‌های ورود تابع `suspend` هستند: آن‌ها را از داخل یک coroutine صدا بزنید (مثلاً `lifecycleScope.launch { ... }`).

ورود

```kotlin val auth = AbrovaAuth.shared val methods = auth.getMethods() if (auth.currentUserId == null && methods.isEnabled("anonymous")) { auth.signInAnonymously() } Log.d("Auth", auth.currentUserId!!) // ru_... ``` فقط دکمه‌های روش‌هایی را نشان دهید که `methods.isEnabled(...)` برایشان true است: `anonymous`، `apple`، `google`، `password`، `magicLink`، `telegram`.

استفاده از کاربر با SDKهای دیگر ابرووا

Abrova Sync، Chat، Push، Storage و A/B Testing به‌زودی می‌آیند. فراخوانی‌های زیر نشان می‌دهند کاربر Auth را چطور خواهند گرفت؛ امروز کاربر را با `AbrovaTrace.setUserId(auth.currentUserId)` به ابرووا تریس وصل کنید. ```kotlin // Abrova Sync: its provider is called off the main thread AbrovaSyncConfig.builder(apiKey).userTokenProvider(auth.blockingTokenProvider).build() // Abrova Push AbrovaPushConfig(apiKey = apiKey, tokenProvider = auth.tokenProvider) // Abrova Chat: create the client after sign-in AbrovaChatConfig(apiKey = apiKey, userId = auth.currentUserId!!, tokenProvider = auth.strictTokenProvider) ``` String?', 'null برمی‌گرداند'], ['strictTokenProvider', 'suspend () -> String', 'خطای NOT_SIGNED_IN می‌دهد'], ['blockingTokenProvider', '() -> String?', 'null برمی‌گرداند. thread فراخواننده را block می‌کند: هرگز آن را روی main thread صدا نزنید'], ]} /> همه توکنی برمی‌گردانند که پیش از انقضا تمدید شده است. بعد از ورود یا خروج، متد refresh آن SDK را صدا بزنید (مثلاً `sync.userTokens.refresh()`). Abrova A/B Testing و موارد دیگر: [استفاده با محصولات دیگر ابرووا](/docs/auth/other-products). ### روش‌های ورود هر روش صفحه‌ی خودش را با راه‌اندازی کنسول دارد: [ناشناس](/docs/auth/anonymous)، [Apple](/docs/auth/apple)، [Google](/docs/auth/google)، [ایمیل و رمز عبور](/docs/auth/email-password)، [لینک یک‌بارمصرف](/docs/auth/magic-link)، [تلگرام](/docs/auth/telegram). #### ورود با Google از Credential Manager با client ID **وب** خودتان و یک nonce هش‌شده استفاده کنید. `androidx.credentials:credentials`، `androidx.credentials:credentials-play-services-auth` و `com.google.android.libraries.identity.googleid:googleid` را به اپ اضافه کنید: ```kotlin val rawNonce = AbrovaNonce.generate() val option = GetGoogleIdOption.Builder() .setServerClientId(WEB_CLIENT_ID) .setFilterByAuthorizedAccounts(false) .setNonce(AbrovaNonce.sha256(rawNonce)) .build() val response = CredentialManager.create(context) .getCredential(context, GetCredentialRequest.Builder().addCredentialOption(option).build()) val idToken = GoogleIdTokenCredential.createFrom(response.credential.data).idToken auth.signInWithGoogle(idToken, rawNonce) ``` client ID وب را در **Abrova Auth › روش‌های ورود › Google** اضافه کنید. ورود با Google به اتصال به Google، و در Android به Google Play services هم، نیاز دارد؛ بعضی کاربران در ایران هیچ‌کدام را ندارند. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف و کد تلگرام بدون Google کار می‌کنند، پس دست‌کم یکی از آن‌ها را هم ارائه کنید. [ورود با Google](/docs/auth/google) را ببینید. #### ورود با Apple ورود وب Apple را با **Services ID** خودتان و `AbrovaNonce.sha256(rawNonce)` به‌عنوان nonce اجرا کنید، سپس `id_token` و `code` را که Apple برمی‌گرداند بفرستید: ```kotlin auth.signInWithApple(idToken, rawNonce, authorizationCode = code) ``` #### ایمیل و رمز عبور ```kotlin val result = auth.signUp(email, password, redirectUrl = "https://example.com/auth/verify") // result.signedIn is false when the project requires a verified e-mail first. auth.signIn(email, password) auth.sendPasswordReset(email, redirectUrl = "https://example.com/auth/reset") auth.resetPassword(token, newPassword) // ends every session; does not sign in auth.verifyEmail(token) auth.sendEmailVerification(redirectUrl = "https://example.com/auth/verify") ``` #### لینک یک‌بارمصرف ```kotlin auth.sendMagicLink(email, redirectUrl = "myapp://auth/magic") // In the activity the link opens: AbrovaAuth.tokenFromLink(intent.dataString ?: "")?.let { auth.signInWithMagicLink(it) } ``` #### تلگرام ```kotlin val code = auth.requestTelegramCode("+989121234567") if (!code.sent) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(code.botUrl))) auth.verifyTelegramCode("+989121234567", "123456") ``` ### ارتقای کاربر ناشناس وصل کردن شناسه‌ی کاربر را حفظ می‌کند، پس هر چه ساخته مال خودش می‌ماند: ```kotlin try { auth.linkGoogle(idToken, rawNonce) // or linkApple(...), linkPassword(email, password) } catch (e: AbrovaAuthException) { if (e.code == AbrovaAuthErrorCode.PROVIDER_ALREADY_LINKED) { // This Google account already belongs to another user. } } ``` ### کاربر فعلی ```kotlin auth.currentUserId // null when signed out auth.isSignedIn val user = auth.me() // AbrovaUser: id, isAnonymous, emailVerified, createdAt, providers auth.currentUser // the profile last loaded by me() // StateFlow of the user id lifecycleScope.launch { auth.authState.collect { userId -> /* sign-in, sign-out, session end */ } } // Or a listener val registration = auth.addAuthStateListener { userId -> } registration.remove() val token = auth.getAccessToken() // if you call your own API with it ``` ### خروج و حذف حساب ```kotlin auth.signOut() // this device; never fails auth.signOutEverywhere() // every device auth.deleteAccount() // deletes the user, then signs out ``` برای اینکه حذف چه چیزهایی را پاک می‌کند، [امنیت و داده‌ها](/docs/auth/security) را ببینید. ### مدیریت خطاها همه‌ی خطاها از نوع `AbrovaAuthException` هستند. بر اساس `code` تصمیم بگیرید: ```kotlin try { auth.signIn(email, password) } catch (e: AbrovaAuthException) { when (e.code) { AbrovaAuthErrorCode.INVALID_CREDENTIALS -> showError("Wrong e-mail or password") AbrovaAuthErrorCode.RATE_LIMITED -> showError("Try again in ${e.retryAfterSec ?: 60} s") AbrovaAuthErrorCode.NETWORK_ERROR -> showError("No connection") else -> showError("Sign-in failed") } } ``` `message` یک راهنمای انگلیسی برای توسعه‌دهنده است. SDK علاوه بر کدهای سرور از `NETWORK_ERROR`، `NOT_SIGNED_IN` و `SDK_NOT_CONFIGURED` (اول `configure` را صدا بزنید) استفاده می‌کند. خطای شبکه هرگز کاربر را خارج نمی‌کند. همه‌ی کدها: [کدهای خطا](/docs/auth/error-codes). ### عیب‌یابی

AbrovaAuth.shared.configure(context, apiKey) را در Application.onCreate صدا بزنید.

Credential Manager، ID token را برای client ID وبی صادر می‌کند که به‌عنوان serverClientId می‌دهید. همان client ID وب را در کنسول اضافه کنید و AbrovaNonce.sha256(rawNonce) را به‌عنوان nonce بدهید.

blockingTokenProvider thread خودش را block می‌کند. آن را فقط به SDKهایی بدهید که بیرون از main thread صدایش می‌زنند، و هرگز خودتان آن را روی main thread صدا نزنید.

- SDK وب: https://abrova.ir/docs/auth/web این صفحه `@abrova/auth-web` نسخه‌ی 0.1.1 را توضیح می‌دهد. مرورگری با `fetch` و `crypto.getRandomValues` (همه‌ی مرورگرهای فعلی). فقط در کد سمت کلاینت اجرا می‌شود؛ Abrova Auth SDK سرور ندارد. از دستیار هوش مصنوعی استفاده می‌کنید؟ این صفحه را با **Copy page for AI** به آن بدهید، یا همه‌ی مستندات را به‌صورت متن ساده از [/docs/llms.txt](/docs/llms.txt) بگیرید. [دستیارهای هوش مصنوعی](/docs/auth/ai-assistants) را ببینید.

نصب

پکیج از Abrova Center می‌آید. اول در `.npmrc` پروژه، scope `@abrova` را به رجیستری ابرووا ارجاع دهید: {`@abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/`} سپس نصب کنید: ```bash npm install @abrova/auth-web ``` یا در `package.json`: ```json { "dependencies": { "@abrova/auth-web": "^0.1.1" } } ``` در کنسول ابرووا، **Abrova Auth** را برای پروژه (**تنظیمات › محصولات**) و روش‌های ورود دلخواه را (**Abrova Auth › روش‌های ورود**) روشن کنید.

مقداردهی اولیه

برای اپ یک کلاینت با کلید API عمومی (`ab_live_...`) بسازید: ```typescript import { createAbrovaAuth } from '@abrova/auth-web'; export const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key' }); auth.onAuthStateChanged((userId) => { console.log(userId ? `signed in as ${userId}` : 'signed out'); }); ``` `await auth.ready()` وقتی نشست ذخیره‌شده (اگر باشد) بارگذاری شد resolve می‌شود. `onAuthStateChanged` در همان لحظه listener شما را با شناسه‌ی کاربر فعلی صدا می‌زند، و دوباره در هر ورود یا خروج. یک تابع برمی‌گرداند که گوش دادن را متوقف می‌کند.

ورود

```typescript const { methods } = await auth.getMethods(); // e.g. ['anonymous', 'google', 'password'] await auth.ready(); if (!auth.isSignedIn && methods.includes('anonymous')) { await auth.signInAnonymously(); } console.log(auth.currentUserId); // ru_... ``` فقط دکمه‌های روش‌هایی را نشان دهید که `getMethods()` برمی‌گرداند.

استفاده از کاربر با SDKهای دیگر ابرووا

Abrova Sync، Chat، Push، Storage و A/B Testing به‌زودی می‌آیند. فراخوانی‌های زیر نشان می‌دهند کاربر Auth را چطور خواهند گرفت؛ امروز کاربر را با SDK Next.js ابرووا تریس (`setUserId(auth.currentUserId)`) به تریس وصل کنید. ```typescript import { AbrovaSync } from '@abrova/sync-web'; // Sync, Push and A/B Testing: null while signed out const sync = new AbrovaSync({ apiKey: 'ab_live_your_api_key', tokenProvider: () => auth.getToken() }); ``` `auth.getToken()` به یک access token تازه resolve می‌شود، یا وقتی کسی وارد نشده به `null`. `auth.tokenProvider` به توکن resolve می‌شود و وقتی کسی وارد نشده با `UNAUTHENTICATED` reject می‌شود: از آن برای Abrova Chat استفاده کنید که provider آن باید توکن برگرداند. بعد از ورود یا خروج، متد refresh آن SDK را صدا بزنید (مثلاً `sync.refreshUserToken()`). بیشتر: [استفاده با محصولات دیگر ابرووا](/docs/auth/other-products). ### روش‌های ورود هر روش صفحه‌ی خودش را با راه‌اندازی کنسول دارد: [ناشناس](/docs/auth/anonymous)، [Apple](/docs/auth/apple)، [Google](/docs/auth/google)، [ایمیل و رمز عبور](/docs/auth/email-password)، [لینک یک‌بارمصرف](/docs/auth/magic-link)، [تلگرام](/docs/auth/telegram). #### ایمیل و رمز عبور ```typescript const result = await auth.signUpWithEmail({ email, password, redirectUrl: 'https://example.com/auth/verify' }); // result.signedIn is false when the project requires a verified e-mail first. await auth.signInWithEmail({ email, password }); await auth.sendPasswordReset({ email, redirectUrl: 'https://example.com/auth/reset' }); await auth.resetPassword({ token, password }); // token from the link's ?token= await auth.verifyEmail({ token }); await auth.sendEmailVerification({ redirectUrl: 'https://example.com/auth/verify' }); ``` #### لینک یک‌بارمصرف ```typescript await auth.sendMagicLink({ email, redirectUrl: 'https://example.com/auth/magic' }); // On the page the link opens: const token = new URL(location.href).searchParams.get('token'); if (token) await auth.signInWithMagicLink({ token }); ``` #### تلگرام ```typescript const { sent, botUrl } = await auth.requestTelegramCode({ phone: '+989121234567' }); if (!sent && botUrl) window.open(botUrl); // the user starts the bot and shares the number await auth.verifyTelegramCode({ phone: '+989121234567', code: '123456' }); ``` #### Apple و Google ورودِ خودِ ارائه‌دهنده را با nonce‌ای از SDK اجرا کنید، سپس ID token آن را به ابرووا بدهید: ```typescript const { rawNonce, hashedNonce } = await auth.createNonce(); // Give hashedNonce to Google Identity Services or Sign in with Apple JS as the `nonce`. await auth.signInWithGoogle({ idToken, rawNonce }); await auth.signInWithApple({ idToken, rawNonce, authorizationCode, name }); ``` برای هر ورود یک nonce جدید بسازید. ورود با Google به اتصال به Google نیاز دارد که بعضی کاربران در ایران ندارند. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف و کد تلگرام بدون Google کار می‌کنند، پس دست‌کم یکی از آن‌ها را هم ارائه کنید. [ورود با Google](/docs/auth/google) را ببینید. روی وب، Apple از **Services ID** شما استفاده می‌کند: آن را در **Abrova Auth › روش‌های ورود › Apple** اضافه کنید. ### ارتقای کاربر ناشناس وصل کردن شناسه‌ی کاربر را حفظ می‌کند، پس داده‌های کاربر مال خودش می‌ماند: ```typescript import { isAbrovaAuthError } from '@abrova/auth-web'; try { await auth.linkGoogle({ idToken, rawNonce }); // or linkApple(...), linkPassword({ email, password }) } catch (e) { if (isAbrovaAuthError(e) && e.code === 'PROVIDER_ALREADY_LINKED') { // This Google account already belongs to another user. } } ``` ### کاربر فعلی ```typescript auth.currentUserId; // null when signed out auth.isSignedIn; const me = await auth.getMe(); // { id, anonymous, emailVerified, createdAt, providers } auth.currentUser; // the user last loaded by getMe() ``` ### خروج و حذف حساب ```typescript await auth.signOut(); // this device await auth.signOutEverywhere(); // every device await auth.deleteAccount(); // deletes the user, then signs out ``` ### وارد ماندن به‌طور پیش‌فرض چیزی ذخیره نمی‌شود: کاربر بعد از reload صفحه دوباره وارد می‌شود. برای نگه داشتن نشست، `localStorage` را فعال کنید: ```typescript import { createAbrovaAuth, localStorageStorage } from '@abrova/auth-web'; const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key', storage: localStorageStorage() }); ``` هر اسکریپتی در صفحه‌ی شما می‌تواند `localStorage` را بخواند. یک باگ XSS یا یک وابستگی آلوده می‌تواند refresh token و همراه آن نشست کاربر را بردارد. از این گزینه فقط با یک Content Security Policy سخت‌گیرانه و بدون اسکریپت‌های نامطمئن استفاده کنید. SDK هرگز cookie تنظیم نمی‌کند. می‌توانید حافظه‌ی دلخواه خودتان را هم بدهید: شیئی با متدهای async به نام `get`، `set` و `remove`. ### مدیریت خطاها هر شکست یک `AbrovaAuthError` با یک `code` است: ```typescript import { isAbrovaAuthError } from '@abrova/auth-web'; try { await auth.signInWithEmail({ email, password }); } catch (e) { if (isAbrovaAuthError(e) && e.code === 'INVALID_CREDENTIALS') showError('Wrong e-mail or password'); if (isAbrovaAuthError(e) && e.code === 'RATE_LIMITED') retryIn(e.retryAfterSec); } ``` SDK علاوه بر کدهای سرور از `NETWORK_ERROR` استفاده می‌کند. خطای شبکه هرگز کاربر را خارج نمی‌کند؛ نشستِ لغوشده یا منقضی این کار را می‌کند (`onAuthStateChanged(null)`). access token ۲ دقیقه پیش از انقضا تمدید می‌شود و فراخوانی‌های هم‌زمان در یک تمدید شریک می‌شوند. همه‌ی کدها: [کدهای خطا](/docs/auth/error-codes). ### عیب‌یابی

این رفتار پیش‌فرض است: نشست در حافظه نگه داشته می‌شود. اگر صفحه‌تان Content Security Policy سخت‌گیرانه دارد، storage: localStorageStorage() را بدهید، یا دوباره به‌صورت ناشناس وارد شوید.

نشست ذخیره‌شده به‌صورت async بارگذاری می‌شود. منتظر auth.ready() یا اولین فراخوانی onAuthStateChanged بمانید.

redirectUrl باید با یکی از آدرس‌های لینک در Abrova Auth › تنظیمات شروع شود.

- SDK ری‌اکت نیتیو: https://abrova.ir/docs/auth/react-native این صفحه `@abrova/auth-react-native` نسخه‌ی 0.1.1 را توضیح می‌دهد. React Native با `react-native-keychain`، یا Expo با `expo-secure-store`. ورود با Apple و Google به `crypto.getRandomValues` (`react-native-get-random-values`) هم نیاز دارد. از دستیار هوش مصنوعی استفاده می‌کنید؟ این صفحه را با **Copy page for AI** به آن بدهید، یا همه‌ی مستندات را به‌صورت متن ساده از [/docs/llms.txt](/docs/llms.txt) بگیرید. [دستیارهای هوش مصنوعی](/docs/auth/ai-assistants) را ببینید.

نصب

پکیج از Abrova Center می‌آید. اول در `.npmrc` پروژه، scope `@abrova` را به رجیستری ابرووا ارجاع دهید: {`@abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/`} سپس آن را همراه یکی از کتابخانه‌های حافظه‌ی امن نصب کنید: ```bash npm install @abrova/auth-react-native react-native-keychain # or, with Expo npx expo install @abrova/auth-react-native expo-secure-store ``` یا در `package.json`: ```json { "dependencies": { "@abrova/auth-react-native": "^0.1.1" } } ``` برای nonce ورود با Apple و Google، `react-native-get-random-values` را هم نصب کنید و آن را در ابتدای `index.js` import کنید: ```bash npm install react-native-get-random-values ``` ```typescript // index.js, before anything else import 'react-native-get-random-values'; ``` در کنسول ابرووا، **Abrova Auth** را برای پروژه (**تنظیمات › محصولات**) و روش‌های ورود دلخواه را (**Abrova Auth › روش‌های ورود**) روشن کنید.

مقداردهی اولیه

برای اپ یک کلاینت با کلید API عمومی (`ab_live_...`) و حافظه‌ی امن بسازید: ```typescript import * as Keychain from 'react-native-keychain'; import { createAbrovaAuth, keychainStorage } from '@abrova/auth-react-native'; export const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key', storage: keychainStorage(Keychain), }); auth.onAuthStateChanged((userId) => { console.log(userId ? `signed in as ${userId}` : 'signed out'); }); ``` با Expo: ```typescript import * as SecureStore from 'expo-secure-store'; import { createAbrovaAuth, secureStoreStorage } from '@abrova/auth-react-native'; export const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key', storage: secureStoreStorage(SecureStore) }); ``` فقط refresh token ذخیره می‌شود (Keychain در iOS، حافظه‌ی متکی به Keystore در Android، فقط روی همین دستگاه). access token در حافظه می‌ماند. **بدون `storage`، هیچ چیز بعد از راه‌اندازی دوباره‌ی اپ باقی نمی‌ماند.** `await auth.ready()` وقتی نشست ذخیره‌شده بارگذاری شد resolve می‌شود. `onAuthStateChanged` در همان لحظه listener شما را با شناسه‌ی کاربر فعلی صدا می‌زند، و دوباره در هر ورود یا خروج.

ورود

```typescript const { methods } = await auth.getMethods(); // e.g. ['anonymous', 'apple', 'google'] await auth.ready(); if (!auth.isSignedIn && methods.includes('anonymous')) { await auth.signInAnonymously(); } console.log(auth.currentUserId); // ru_... ```

استفاده از کاربر با SDKهای دیگر ابرووا

Abrova Sync، Chat، Push، Storage و A/B Testing به‌زودی می‌آیند. فراخوانی‌های زیر نشان می‌دهند کاربر Auth را چطور خواهند گرفت؛ هنوز در دسترس نیستند. ```typescript import AbrovaSync from '@abrova/sync-react-native'; // Sync, Push and A/B Testing: null while signed out await AbrovaSync.init({ apiKey: 'ab_live_your_api_key', tokenProvider: () => auth.getToken() }); ``` `auth.getToken()` به یک access token تازه resolve می‌شود، یا وقتی کسی وارد نشده به `null`. `auth.tokenProvider` به جای آن با `UNAUTHENTICATED` reject می‌شود: از آن برای Abrova Chat استفاده کنید. وقتی کاربر وارد یا خارج می‌شود، از SDKهای دیگر بخواهید توکن جدید بگیرند (مثلاً `AbrovaSync.refreshUserToken()`). بیشتر: [استفاده با محصولات دیگر ابرووا](/docs/auth/other-products). ### روش‌های ورود هر روش صفحه‌ی خودش را با راه‌اندازی کنسول دارد: [ناشناس](/docs/auth/anonymous)، [Apple](/docs/auth/apple)، [Google](/docs/auth/google)، [ایمیل و رمز عبور](/docs/auth/email-password)، [لینک یک‌بارمصرف](/docs/auth/magic-link)، [تلگرام](/docs/auth/telegram). #### ایمیل و رمز عبور ```typescript const result = await auth.signUpWithEmail({ email, password, redirectUrl: 'myapp://auth/verify' }); // result.signedIn is false when the project requires a verified e-mail first. await auth.signInWithEmail({ email, password }); await auth.sendPasswordReset({ email, redirectUrl: 'myapp://auth/reset' }); await auth.resetPassword({ token, password }); // token from the link's ?token= await auth.verifyEmail({ token }); ``` #### لینک یک‌بارمصرف ```typescript await auth.sendMagicLink({ email, redirectUrl: 'myapp://auth/magic' }); // When the app is opened with the link (Linking): const token = new URL(url).searchParams.get('token'); if (token) await auth.signInWithMagicLink({ token }); ``` #### تلگرام ```typescript import { Linking } from 'react-native'; const { sent, botUrl } = await auth.requestTelegramCode({ phone: '+989121234567' }); if (!sent && botUrl) await Linking.openURL(botUrl); await auth.verifyTelegramCode({ phone: '+989121234567', code: '123456' }); ``` #### Apple و Google ورود بومی را با nonce‌ای از SDK اجرا کنید، سپس ID token آن را به ابرووا بدهید: ```typescript const { rawNonce, hashedNonce } = await auth.createNonce(); // Give hashedNonce to your Apple / Google sign-in library as the `nonce`. await auth.signInWithApple({ idToken, rawNonce, authorizationCode }); await auth.signInWithGoogle({ idToken, rawNonce }); ``` برای هر ورود یک nonce جدید بسازید. ورود با Google به اتصال به Google، و در Android به Google Play services هم، نیاز دارد؛ بعضی کاربران در ایران هیچ‌کدام را ندارند. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف و کد تلگرام بدون Google کار می‌کنند، پس دست‌کم یکی از آن‌ها را هم ارائه کنید. [ورود با Google](/docs/auth/google) را ببینید. ### ارتقای کاربر ناشناس وصل کردن شناسه‌ی کاربر را حفظ می‌کند، پس داده‌های کاربر مال خودش می‌ماند: ```typescript import { isAbrovaAuthError } from '@abrova/auth-react-native'; try { await auth.linkApple({ idToken, rawNonce }); // or linkGoogle(...), linkPassword({ email, password }) } catch (e) { if (isAbrovaAuthError(e) && e.code === 'PROVIDER_ALREADY_LINKED') { // This Apple ID already belongs to another user. } } ``` ### کاربر فعلی ```typescript auth.currentUserId; // null when signed out auth.isSignedIn; const me = await auth.getMe(); // { id, anonymous, emailVerified, createdAt, providers } auth.currentUser; // the user last loaded by getMe() ``` ### خروج و حذف حساب ```typescript await auth.signOut(); // this device await auth.signOutEverywhere(); // every device await auth.deleteAccount(); // deletes the user, then signs out (required by the App Store) ``` ### مدیریت خطاها هر شکست یک `AbrovaAuthError` با یک `code` است: ```typescript import { isAbrovaAuthError } from '@abrova/auth-react-native'; try { await auth.signInWithEmail({ email, password }); } catch (e) { if (isAbrovaAuthError(e) && e.code === 'INVALID_CREDENTIALS') showError('Wrong e-mail or password'); if (isAbrovaAuthError(e) && e.code === 'RATE_LIMITED') retryIn(e.retryAfterSec); } ``` SDK علاوه بر کدهای سرور از `NETWORK_ERROR` استفاده می‌کند. خطای شبکه هرگز کاربر را خارج نمی‌کند؛ نشستِ لغوشده یا منقضی این کار را می‌کند (`onAuthStateChanged(null)`). access token ۲ دقیقه پیش از انقضا تمدید می‌شود و فراخوانی‌های هم‌زمان در یک تمدید شریک می‌شوند. همه‌ی کدها: [کدهای خطا](/docs/auth/error-codes). ### عیب‌یابی

storage: keychainStorage(Keychain) یا secureStoreStorage(SecureStore) را بدهید. بدون آن، نشست فقط در حافظه نگه داشته می‌شود.

crypto.getRandomValues وجود ندارد. react-native-get-random-values را نصب کنید و آن را در ابتدای index.js import کنید.

redirectUrl باید با یکی از آدرس‌های لینک در Abrova Auth › تنظیمات شروع شود، مثلاً myapp://auth.

- دستیارهای هوش مصنوعی (به‌زودی): https://abrova.ir/docs/auth/ai-assistants هنوز سرور مستندات ابرووا برای دستیارهای هوش مصنوعی وجود ندارد. این صفحه می‌گوید چه کاری خواهد کرد و امروز چه چیزی کار می‌کند. ### چه کاری خواهد کرد وقتی راه بیفتد، دستیار هوش مصنوعی‌تان (مثلاً دستیار کدنویسی داخل ادیتور) را یک بار به آن وصل می‌کنید. وقتی از دستیار بخواهید Abrova Auth را به اپ‌تان اضافه کند، به جای جواب دادن از حافظه، نسخه‌ی فعلی SDK، خط‌های نصب و مراحل راه‌اندازی را از همین مستندات پیدا می‌کند. این سرور فقط‌خواندنی خواهد بود و ابزارهایی مثل این‌ها دارد: ### حریم خصوصی - **چه چیزی را می‌خواند:** مستندات عمومی ابرووا، نسخه‌های فعلی SDKها و کلماتی که دستیارتان برایش می‌فرستد. - **هرگز نمی‌بیند:** کد شما، پروژه‌ها، کلیدها یا کاربران‌تان. هرگز کلیدی را در گفتگو با دستیار هوش مصنوعی جای‌گذاری نکنید. بگذارید placeholder مستندات (`ab_live_your_api_key`) را بنویسد و مقدار واقعی را خودتان در تنظیمات پروژه قرار دهید. ### امروز چه چیزی کار می‌کند - **Copy page for AI** در هر صفحه‌ی مستندات، آن صفحه را به‌صورت متن ساده کپی می‌کند تا در دستیارتان جای‌گذاری کنید. - در [/docs/llms.txt](/docs/llms.txt) همه‌ی صفحه‌های مستندات به‌صورت متن ساده هست، برای دستیارهایی که می‌توانند لینک بخوانند. صفحه‌های خوب برای شروع: [معرفی و شروع سریع](/docs/auth/quick-start) و صفحه‌ی SDK پلتفرم‌تان ([Flutter](/docs/auth/flutter)، [iOS](/docs/auth/ios)، [Android](/docs/auth/android)، [Web](/docs/auth/web)، [React Native](/docs/auth/react-native)). درباره‌ی دستیارهای هوش مصنوعی و ابرووا بیشتر بخوانید: [دستیارهای هوش مصنوعی](/ai-assistants). - ناشناس: https://abrova.ir/docs/auth/anonymous یک فراخوانی کاربری با شناسه‌ی تصادفی می‌سازد و دستگاه را وارد می‌کند. نه صفحه‌ای هست و نه چیزی برای تایپ کردن. نشست فقط روی همین دستگاه است، پس تا وقتی کاربر [یک روش ورود وصل نکرده](/docs/auth/upgrading)، او را مهمان در نظر بگیرید.

روشن کردن

در کنسول ابرووا، **Abrova Auth › روش‌های ورود** را باز کنید و **ناشناس** را روشن کنید. در صورت تمایل، در **Abrova Auth › تنظیمات › کاربران ناشناس** سقف روزانه‌ی کمتری برای کاربران ناشناس جدید بگذارید.

ورود

فقط وقتی کسی هنوز وارد نشده، ورود را انجام دهید. SDK نشست ذخیره‌شده را هنگام اجرا بازیابی می‌کند، پس همان مهمان در اجرای بعدی برمی‌گردد. ```dart // Flutter final auth = AbrovaAuth.instance; if (auth.currentUser == null) { await auth.signInAnonymously(); } ``` ```swift // iOS if AbrovaAuth.shared.currentUserId == nil { try await AbrovaAuth.shared.signInAnonymously() } ``` ```kotlin // Android if (AbrovaAuth.shared.currentUserId == null) { AbrovaAuth.shared.signInAnonymously() } ``` ```typescript // Web and React Native await auth.ready(); // the saved session is loaded if (!auth.isSignedIn) { await auth.signInAnonymously(); } ``` در Flutter، اگر از قبل یک کاربر ناشناس روی دستگاه وارد شده باشد، `signInAnonymously()` همان کاربر فعلی را برمی‌گرداند.

ارتقا در آینده

وقتی کاربر ناشناس خواست حسابش را نگه دارد، Apple، Google یا ایمیل و رمز عبور را به او وصل کنید. شناسه همان می‌ماند: ```dart await AbrovaAuth.instance.linkPassword(email: email, password: password); ``` برای اینکه وقتی حساب از قبل وجود دارد چه اتفاقی می‌افتد، [کاربران ناشناس و ارتقا](/docs/auth/upgrading) را ببینید. ### سقف‌ها با عبور از سقف، فراخوانی با `RATE_LIMITED` شکست می‌خورد. هر کاربر ناشناس جدیدی که از اپ استفاده کند، مثل هر کاربر دیگری یک کاربر فعال ماهانه حساب می‌شود. ### خطاها - Apple: https://abrova.ir/docs/auth/apple اپ شما ورودِ خودِ Apple را اجرا می‌کند و یک **ID token** از Apple می‌گیرد. SDK این توکن را به Abrova Auth می‌فرستد؛ Abrova Auth بررسی می‌کند که Apple آن را برای یکی از شناسه‌های اپِ **شما** امضا کرده باشد و کاربر را وارد می‌کند. در iOS، SDK می‌تواند کل صفحه‌ی Apple را برایتان اجرا کند. اپ iOS که ورود با Google (یا ورود شخص ثالث دیگری) دارد باید ورود با Apple را هم ارائه کند، و اپی که اجازه‌ی ساخت حساب می‌دهد باید اجازه‌ی حذف آن را هم داخل اپ بدهد. Abrova Auth از هر دو پشتیبانی می‌کند. ورود با Apple به اتصال به Apple نیاز دارد که از ایران ممکن است ناپایدار باشد. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف یا کد تلگرام را هم ارائه کنید: این روش‌ها به Apple یا Google وابسته نیستند.

راه‌اندازی در کنسول

**Abrova Auth › روش‌های ورود** را باز کنید، **Apple** را روشن کنید و شناسه‌هایتان را وارد کنید: Abrova Auth فقط ID tokenی از Apple را می‌پذیرد که برای یکی از این شناسه‌ها صادر شده باشد. توکنی که Apple برای هر اپ دیگری صادر کرده رد می‌شود. #### کلید حذف حساب (توصیه‌شده) Apple از اپ‌ها می‌خواهد وقتی کاربر حسابش را حذف می‌کند، ورود با Apple او را لغو کنند. برای اینکه Abrova Auth این کار را انجام دهد، در Apple Developer portal یک **Sign in with Apple key** بسازید (Certificates, Identifiers & Profiles › Keys) و در همان بخش Apple این‌ها را وارد کنید: سپس هنگام ورود کاربر، `authorizationCode` مربوط به Apple را بفرستید (SDKها نشان می‌دهند کجا). Abrova Auth این کد را وقتی کاربر برای اولین بار با Apple ثبت‌نام می‌کند (یا Apple را وصل می‌کند) استفاده می‌کند، پس کلید را **پیش از** انتشار اپ اضافه کنید: ورود با Apple کاربرانی که پیش از ذخیره‌ی کلید ثبت‌نام کرده‌اند قابل لغو نیست. بدون کلید یا کد هم حذف حساب کاربر را حذف می‌کند؛ فقط لغو در سمت Apple انجام نمی‌شود.

مقدار nonce

nonce، ID token مربوط به Apple را به همین یک ورود گره می‌زند تا توکنی که شنود شده دوباره قابل استفاده نباشد: 1. با تابع کمکی SDK یک **nonce خام** تصادفی بسازید (۱۶ تا ۲۵۶ کاراکتر). 2. SHA-256 آن را به‌صورت hex با حروف کوچک به **Apple** بدهید. 3. مقدار خام را به **Abrova Auth** بدهید. Abrova Auth بررسی می‌کند که این دو با هم جور باشند و هر nonce را فقط یک بار می‌پذیرد. ID token مربوط به Apple هم باید کمتر از ۱۰ دقیقه عمر داشته باشد.

ورود

#### iOS: SDK صفحه‌ی Apple را اجرا می‌کند قابلیت **Sign in with Apple** را به target اپ اضافه کنید، سپس: ```swift try await AbrovaAuth.shared.signInWithAppleNative() ``` SDK خودش nonce را می‌سازد، صفحه‌ی Apple را نشان می‌دهد و ID token، کد authorization و نامی را که Apple در اولین ورود می‌دهد ارسال می‌کند. وقتی کاربر صفحه را ببندد، خطای `.canceled` می‌دهد. #### Flutter با پکیج `sign_in_with_apple`: ```dart final raw = AbrovaAuthNonce.generate(); final apple = await SignInWithApple.getAppleIDCredential( scopes: [AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName], nonce: AbrovaAuthNonce.sha256Hex(raw), ); await AbrovaAuth.instance.signInWithApple( idToken: apple.identityToken!, rawNonce: raw, authorizationCode: apple.authorizationCode, // lets account deletion revoke Apple name: [apple.givenName, apple.familyName].whereType().join(' '), ); ``` #### Android ورود وب Apple را با **Services ID** خودتان و `AbrovaNonce.sha256(rawNonce)` به‌عنوان nonce اجرا کنید، سپس `id_token` و `code` را که Apple برمی‌گرداند بفرستید: ```kotlin AbrovaAuth.shared.signInWithApple(idToken, rawNonce, authorizationCode = code) ``` #### Web و React Native از Sign in with Apple JS (وب) یا کتابخانه‌ی ورود با Apple در React Native استفاده کنید و `hashedNonce` را به‌عنوان nonce بدهید: ```typescript const { rawNonce, hashedNonce } = await auth.createNonce(); // ... run Apple's sign-in with nonce: hashedNonce, then: await auth.signInWithApple({ idToken, rawNonce, authorizationCode, name }); ``` ### نام و ایمیل Apple نام کاربر را فقط در **اولین** ورود می‌دهد، و ایمیل ممکن است یک آدرس private relay باشد. Abrova Auth آن‌ها را (رمزگذاری‌شده) فقط وقتی ذخیره می‌کند که **ذخیره‌ی اطلاعات پروفایل** در **Abrova Auth › تنظیمات** روشن باشد. اگر اپ شما به نام نیاز دارد، آن را در اولین ورود در داده‌های خودتان ذخیره کنید. ### وصل کردن Apple به کاربرِ واردشده برای ارتقای کاربر ناشناس، فراخوانی link را با همان آرگومان‌ها به کار ببرید: `linkApple(...)`، یا در iOS `signInWithAppleNative(link: true)`. شناسه‌ی کاربر همان می‌ماند. اگر این Apple ID از قبل متعلق به کاربر دیگری باشد، فراخوانی با `PROVIDER_ALREADY_LINKED` شکست می‌خورد. ### خطاها - Google: https://abrova.ir/docs/auth/google اپ شما ورودِ خودِ Google را اجرا می‌کند (Credential Manager در Android، Google Sign-In در iOS و Flutter، و Google Identity Services در وب) و یک **ID token** می‌گیرد. SDK این توکن را به Abrova Auth می‌فرستد؛ Abrova Auth بررسی می‌کند که Google آن را برای یکی از OAuth client IDهای **شما** صادر کرده باشد و کاربر را وارد می‌کند. ورود با Google به اتصال به Google، و در Android به Google Play services هم، نیاز دارد. بعضی کاربران در ایران هیچ‌کدام را ندارند: گوشی‌هایی که بدون Google Play services فروخته شده‌اند، یا سرویس‌های Google که در دسترس نیستند. برای آن‌ها دکمه‌ی Google پیش از اینکه به Abrova Auth برسد شکست می‌خورد. ناشناس، ایمیل و رمز عبور، لینک یک‌بارمصرف و کد تلگرام بدون Google کار می‌کنند، پس دست‌کم یکی از آن‌ها را کنار دکمه‌ی Google بگذارید.

راه‌اندازی در کنسول

1. در Google Cloud Console › **APIs & Services › Credentials**، OAuth client IDهایی را که اپ‌هایتان با آن‌ها وارد می‌شوند پیدا کنید (یا بسازید). Android، iOS و وب هر کدام client ID خودشان را دارند. 2. در کنسول ابرووا، **Abrova Auth › روش‌های ورود** را باز کنید، **Google** را روشن کنید و **همه‌ی** client IDهایی را که اپ‌هایتان استفاده می‌کنند اضافه کنید. Abrova Auth فقط ID tokenی از Google را می‌پذیرد که audience آن یکی از این client IDها باشد. در Android با Credential Manager، و در `google_sign_in` وقتی `serverClientId` را می‌دهید، ID token برای client ID **وب** شما صادر می‌شود. client ID وب را به فهرست اضافه کنید، حتی اگر اپ وب ندارید.

مقدار nonce

با تابع کمکی SDK یک **nonce خام** تصادفی بسازید، SHA-256 آن را به‌صورت hex با حروف کوچک به **Google** بدهید و مقدار خام را به **Abrova Auth**. ابرووا بررسی می‌کند که این دو با هم جور باشند و هر nonce را فقط یک بار می‌پذیرد. ID token باید کمتر از ۱۰ دقیقه عمر داشته باشد. Abrova Auth ورود با Google بدون nonce خام را رد می‌کند (`INVALID_INPUT`)، به همین دلیل همه‌ی SDKها آن را الزامی کرده‌اند.

ورود

#### Android (Credential Manager) `androidx.credentials:credentials`، `androidx.credentials:credentials-play-services-auth` و `com.google.android.libraries.identity.googleid:googleid` را به اپ اضافه کنید: ```kotlin val rawNonce = AbrovaNonce.generate() val option = GetGoogleIdOption.Builder() .setServerClientId(WEB_CLIENT_ID) .setFilterByAuthorizedAccounts(false) .setNonce(AbrovaNonce.sha256(rawNonce)) .build() val response = CredentialManager.create(context) .getCredential(context, GetCredentialRequest.Builder().addCredentialOption(option).build()) val idToken = GoogleIdTokenCredential.createFrom(response.credential.data).idToken AbrovaAuth.shared.signInWithGoogle(idToken, rawNonce) ``` #### Flutter با `google_sign_in` نسخه‌ی ۷: ```dart final raw = AbrovaAuthNonce.generate(); await GoogleSignIn.instance.initialize( serverClientId: 'xxx.apps.googleusercontent.com', nonce: AbrovaAuthNonce.sha256Hex(raw), ); final account = await GoogleSignIn.instance.authenticate(); await AbrovaAuth.instance.signInWithGoogle( idToken: account.authentication.idToken!, rawNonce: raw, ); ``` #### iOS ID token مربوط به Google را با `AbrovaNonce.sha256(rawNonce)` به‌عنوان nonce درخواست کنید، سپس: ```swift try await AbrovaAuth.shared.signInWithGoogle(idToken: idToken, rawNonce: rawNonce) ``` #### Web و React Native `hashedNonce` را به‌عنوان `nonce` به Google Identity Services (وب) یا کتابخانه‌ی ورود با Google در React Native بدهید: ```typescript const { rawNonce, hashedNonce } = await auth.createNonce(); // ... run Google's sign-in with nonce: hashedNonce, then: await auth.signInWithGoogle({ idToken, rawNonce }); ``` ### ایمیل Abrova Auth از ایمیل Google فقط وقتی استفاده می‌کند که Google بگوید تأییدشده است، و آن را (رمزگذاری‌شده) فقط وقتی ذخیره می‌کند که **ذخیره‌ی اطلاعات پروفایل** در **Abrova Auth › تنظیمات** روشن باشد. یک حساب Google و یک حساب Apple یا رمز عبور با همان ایمیل، کاربران جداگانه می‌مانند مگر اینکه کاربر آن‌ها را به هم وصل کند. ### وصل کردن Google به کاربرِ واردشده برای ارتقای کاربر ناشناس، `linkGoogle(...)` را با همان آرگومان‌ها صدا بزنید. شناسه‌ی کاربر همان می‌ماند. اگر این حساب Google از قبل متعلق به کاربر دیگری باشد، فراخوانی با `PROVIDER_ALREADY_LINKED` شکست می‌خورد. ### خطاها - ایمیل و رمز عبور: https://abrova.ir/docs/auth/email-password کاربران با یک آدرس ایمیل و رمز عبور حساب می‌سازند و سپس با همان‌ها وارد می‌شوند. Abrova Auth ایمیل‌های تأیید و بازیابی رمز عبور را می‌فرستد؛ لینک‌ها اپ (یا صفحه‌ی وب) شما را با یک `token` یک‌بارمصرف باز می‌کنند و اپ آن را به SDK برمی‌گرداند.

راه‌اندازی در کنسول

1. **Abrova Auth › روش‌های ورود:** **ایمیل و رمز عبور** را روشن کنید. **کوتاه‌ترین رمز عبور** (۸ تا ۱۲۸ کاراکتر، پیش‌فرض ۸) را تعیین کنید و مشخص کنید آیا کاربران پیش از ورود به **ایمیل تأییدشده** نیاز دارند یا نه. 2. **Abrova Auth › تنظیمات › آدرس‌های لینک:** آدرس‌هایی را که لینک‌های ایمیلی شما اجازه دارند باز کنند اضافه کنید، مثلاً `https://example.com/auth` یا `myapp://auth`. هر کدام باید یک آدرس `https://` یا URL scheme خودِ اپ شما باشد.

ثبت‌نام

```dart // Flutter final result = await AbrovaAuth.instance.signUpWithEmail( email: email, password: password, redirectUrl: 'https://example.com/auth/verify', // optional: sends a verification e-mail ); if (!result.signedIn) { // The project requires a verified e-mail: ask the user to open the link first. } ``` ```swift // iOS let result = try await AbrovaAuth.shared.signUp(email: email, password: password, redirectURL: "https://example.com/auth/verify") ``` ```kotlin // Android val result = AbrovaAuth.shared.signUp(email, password, redirectUrl = "https://example.com/auth/verify") ``` ```typescript // Web and React Native const result = await auth.signUpWithEmail({ email, password, redirectUrl: 'https://example.com/auth/verify' }); ``` نتیجه `verificationSent` (ایمیل تأیید ارسال شده است) و `signedIn` را به شما می‌گوید. وقتی **ایمیل تأییدشده الزامی است** روشن باشد، کاربر تا وقتی لینک را باز نکند و سپس وارد نشود، وارد نمی‌شود. رمز عبور باید دست‌کم به اندازه‌ی کوتاه‌ترین طول تعیین‌شده برای پروژه باشد، حداکثر ۱۲۸ کاراکتر، و جزو رمزهای خیلی رایج نباشد. در غیر این صورت فراخوانی با `WEAK_PASSWORD` شکست می‌خورد. آدرسی که از قبل حساب دارد با `EMAIL_TAKEN` شکست می‌خورد.

ورود

رمز اشتباه و ایمیل ناشناخته هر دو با `INVALID_CREDENTIALS` شکست می‌خورند، تا پاسخ هرگز فاش نکند چه کسی حساب دارد. شکست‌های پشت سر هم برای یک آدرس یا از یک مکان کُند می‌شوند (`RATE_LIMITED`).

تأیید ایمیل

لینک تأیید، `?token=...` را باز می‌کند و **۲۴ ساعت** اعتبار دارد. `token` را بخوانید و به SDK بدهید: تأیید فقط ایمیل را تأییدشده علامت می‌زند؛ کاربر را وارد نمی‌کند. برای ارسال دوباره‌ی ایمیل برای کاربرِ واردشده، `sendEmailVerification` را با یک `redirectUrl` صدا بزنید.

بازیابی رمز عبور فراموش‌شده

1. `sendPasswordReset` را با ایمیل و یک `redirectUrl` صدا بزنید. پاسخ چه آدرس حساب داشته باشد چه نه، یکسان است. 2. ایمیل به `?token=...` لینک می‌دهد که **۳۰ دقیقه** اعتبار دارد و فقط یک بار قابل استفاده است. 3. صفحه‌ی «رمز عبور جدید» را نشان دهید و `resetPassword` را با token و رمز عبور جدید صدا بزنید. ```dart // Flutter await AbrovaAuth.instance.sendPasswordReset(email, redirectUrl: 'https://example.com/auth/reset'); // ... in the screen the link opens: await AbrovaAuth.instance.resetPassword(tokenFromLink, newPassword); ``` بازیابی رمز، **همه‌ی** نشست‌های کاربر را پایان می‌دهد و ایمیل را تأییدشده علامت می‌زند. کاربر را وارد نمی‌کند: کاربر با رمز عبور جدید وارد می‌شود. ### دادن رمز عبور به کاربر ناشناس `linkPassword` یک ایمیل و رمز عبور به کاربرِ واردشده (معمولاً ناشناس) اضافه می‌کند. شناسه همان می‌ماند. وقتی کاربر دیگری آن آدرس را داشته باشد، با `EMAIL_TAKEN` شکست می‌خورد. ### ایمیل‌ها Abrova Auth سه ایمیل می‌فرستد: **تأیید ایمیل**، **بازیابی رمز عبور** و **لینک یک‌بارمصرف**. نام پروژه‌ی شما در آن‌ها می‌آید و از آدرس ابرووا ارسال می‌شوند. ارسال برای هر آدرس (چند عدد در ساعت) و برای هر پروژه محدود است. `redirectUrl`ی که با یکی از آدرس‌های لینک شما شروع نشود، پیش از ارسال هر ایمیلی با `INVALID_INPUT` رد می‌شود. این کار جلوی سوءاستفاده از پروژه‌ی شما برای ارسال لینک‌هایی به مقصد دیگر را می‌گیرد. ### خطاها - لینک یک‌بارمصرف: https://abrova.ir/docs/auth/magic-link کاربر آدرس ایمیلش را تایپ می‌کند و یک لینک ورود دریافت می‌کند. با باز کردن لینک، همراه یک `token` یک‌بارمصرف به اپ شما برمی‌گردد و SDK آن را با یک نشست عوض می‌کند. رمزی برای به خاطر سپردن نیست؛ حساب اولین باری که آن آدرس وارد شود ساخته می‌شود.

راه‌اندازی در کنسول

1. **Abrova Auth › روش‌های ورود:** **لینک یک‌بارمصرف** را روشن کنید. 2. **Abrova Auth › تنظیمات › آدرس‌های لینک:** آدرسی را که لینک باز می‌کند اضافه کنید، مثلاً `https://example.com/auth/magic` یا `myapp://auth/magic`. در موبایل، کاری کنید که آن آدرس اپ شما را باز کند: یک custom URL scheme، یا برای آدرس `https://` یک universal link در Apple یا App Link در Android. ```dart // Flutter await AbrovaAuth.instance.sendMagicLink(email, redirectUrl: 'myapp://auth/magic'); ``` ```swift // iOS try await AbrovaAuth.shared.sendMagicLink(email: email, redirectURL: "myapp://auth/magic") ``` ```kotlin // Android AbrovaAuth.shared.sendMagicLink(email, redirectUrl = "myapp://auth/magic") ``` ```typescript // Web and React Native await auth.sendMagicLink({ email, redirectUrl: 'https://example.com/auth/magic' }); ``` پاسخ چه آدرس حساب داشته باشد چه نه، یکسان است. به کاربر بگویید صندوق ایمیلش را ببیند. لینک، `?token=...` را باز می‌کند. token را بخوانید و وارد شوید: ```dart // Flutter: token from the incoming link await AbrovaAuth.instance.signInWithMagicLink(Uri.parse(link).queryParameters['token']!); ``` ```swift // iOS: pass the URL the app was opened with try await AbrovaAuth.shared.signInWithMagicLink(url: openedURL) ``` ```kotlin // Android: from the intent that opened the activity AbrovaAuth.tokenFromLink(intent.dataString ?: "")?.let { AbrovaAuth.shared.signInWithMagicLink(it) } ``` ```typescript // Web const token = new URL(location.href).searchParams.get('token'); if (token) await auth.signInWithMagicLink({ token }); ``` اولین ورود با یک آدرس، کاربر را می‌سازد و ایمیل را تأییدشده علامت می‌زند. لینک‌های بعدی به همان کاربر وارد می‌شوند. ### قواعد کاربرِ لینک یک‌بارمصرف و کاربرِ ایمیل و رمز عبور با یک آدرس، یک کاربرند: لینک مالکیت آدرس را ثابت می‌کند. کاربرانی که با Apple یا Google وارد شده‌اند جدا هستند، حتی با همان آدرس. ### خطاها - کد تلگرام: https://abrova.ir/docs/auth/telegram کاربر شماره تلفنش را وارد می‌کند و یک کد ورود ۶ رقمی از **ربات تلگرام پروژه‌ی شما** می‌گیرد. جایی که تلگرام رایج است، این روش جای پیامک را می‌گیرد، بدون هزینه برای هر پیام. تلگرام تأیید می‌کند که شماره متعلق به حساب تلگرام کاربر است، پس کد همیشه فقط به صاحب آن شماره می‌رسد. تلگرام در ایران فیلتر است اما پرکاربرد است، و به Google یا Apple وابسته نیست. کاربرانی که تلگرام ندارند به روش دیگری مثل ایمیل یا لینک یک‌بارمصرف نیاز دارند.

ساخت و وصل کردن ربات

1. در تلگرام با **@BotFather** صحبت کنید، `/newbot` را بفرستید و یک نام و username برای ربات‌تان انتخاب کنید (مثلاً `MyAppLoginBot`). BotFather یک **bot token** به شما می‌دهد. 2. در کنسول ابرووا، **Abrova Auth › روش‌های ورود** را باز کنید، **تلگرام** را روشن کنید و bot token را جای‌گذاری کنید. Abrova Auth توکن را با تلگرام بررسی می‌کند و ربات را وصل می‌کند. توکن رمزگذاری‌شده ذخیره می‌شود و دیگر نمایش داده نمی‌شود. از ربات فقط برای Abrova Auth استفاده کنید: ابرووا پیام‌های ورودی آن را در دست می‌گیرد.

کاربر چطور وارد می‌شود

ربات فقط شماره‌ی **خودِ** کاربر را می‌پذیرد که با دکمه‌ی تلگرام به اشتراک گذاشته شده باشد. مخاطبِ فورواردشده‌ی شخص دیگر رد می‌شود.

کد

```dart // Flutter final auth = AbrovaAuth.instance; final r = await auth.requestTelegramCode('+989121234567'); if (!r.sent && r.botUrl != null) { await launchUrl(Uri.parse(r.botUrl!)); // url_launcher: the user starts the bot and shares the number } // ... the user types the code from Telegram: await auth.verifyTelegramCode('+989121234567', code); ``` ```swift // iOS let r = try await AbrovaAuth.shared.requestTelegramCode(phone: "+989121234567") if !r.sent, let url = r.botURL { await UIApplication.shared.open(url) } try await AbrovaAuth.shared.verifyTelegramCode(phone: "+989121234567", code: code) ``` ```kotlin // Android val r = AbrovaAuth.shared.requestTelegramCode("+989121234567") if (!r.sent) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(r.botUrl))) AbrovaAuth.shared.verifyTelegramCode("+989121234567", code) ``` ```typescript // Web and React Native const { sent, botUrl } = await auth.requestTelegramCode({ phone: '+989121234567' }); if (!sent && botUrl) window.open(botUrl); // React Native: Linking.openURL(botUrl) await auth.verifyTelegramCode({ phone: '+989121234567', code }); ``` اولین کد موفق برای یک شماره، کاربر را می‌سازد. کدهای بعدی برای آن شماره به همان کاربر وارد می‌شوند. ### قواعد وقتی تلگرام روشن است، `getMethods()` نام کاربری ربات را به‌صورت `telegramBot` برمی‌گرداند، اگر بخواهید آن را نشان دهید («کدها از @MyAppLoginBot می‌آیند»). ### خطاها - کاربران و نشست‌ها: https://abrova.ir/docs/auth/users-sessions هر **کاربر** Abrova Auth متعلق به یک پروژه است و یک شناسه‌ی تصادفی دارد. **نشست** چیزی است که آن کاربر را روی یک دستگاه وارد نگه می‌دارد: یک access token کوتاه‌مدت برای محصولات ابرووا و یک refresh token بلندمدت که SDK آن را با access tokenهای جدید عوض می‌کند. ### کاربران هر کاربر شناسه‌ای مثل `ru_4fK9xQ2mB7nT1pLz8RcV3w` دارد: `ru_` و ۲۲ حرف و رقم تصادفی. شناسه هرگز ایمیل، شماره تلفن یا شناسه‌ی حساب Apple یا Google را در خود ندارد، پس ذخیره‌اش در داده‌هایتان و نمایشش در logها امن است. - **هر پروژه، یک فهرست کاربر.** یک شخص در دو پروژه‌ی شما، دو کاربر بی‌ارتباط است. کاربرِ یک پروژه هرگز نمی‌تواند به پروژه‌ی دیگر وارد شود. - **چند راه ورود، یک کاربر.** کاربر می‌تواند بیش از یک روش ورود وصل‌شده داشته باشد (مثلاً اول ناشناس، بعد Apple، بعد رمز عبور). شناسه همان می‌ماند. [کاربران ناشناس و ارتقا](/docs/auth/upgrading) را ببینید. - **بدون ادغام خودکار بر اساس ایمیل.** یک حساب Apple و یک حساب Google با ایمیل یکسان دو کاربرند، مگر اینکه کاربر عمداً آن‌ها را به هم وصل کند. ادغام بر اساس ایمیل یک روش شناخته‌شده برای تصاحب حساب‌هاست. SDKها کاربر را به این شکل برمی‌گردانند: ### نشست‌ها هر ورود یک نشست روی آن دستگاه شروع می‌کند. هر نشست دو توکن دارد: | | access token | refresh token | |---|---|---| | چیست | توکن کاربر ابرووا (یک JWT امضاشده) | یک مقدار تصادفی که به‌تنهایی معنایی ندارد | | طول عمر | **یک ساعت** | ۶۰ روز از آخرین استفاده، حداکثر یک سال از زمان ورود | | نگه‌داری روی دستگاه | در حافظه | در حافظه‌ی امن (Keychain، حافظه‌ی متکی به Android Keystore، یا هر چه روی وب انتخاب کنید) | | نگه‌داری در ابرووا | هیچ: با امضایش بررسی می‌شود | فقط هش آن | | فرستاده می‌شود به | همه‌ی محصولات ابرووا | فقط Abrova Auth | کاربری که دست‌کم هر ۶۰ روز یک بار اپ را باز کند، تا یک سال وارد می‌ماند. بعد از یک سال، یا بعد از ۶۰ روز بدون باز کردن اپ، کاربر دوباره وارد می‌شود. #### access token access token همان **توکن کاربر ابرووا** استاندارد است. Abrova Sync، Chat، Push، A/B Testing و Storage امضای آن را بررسی می‌کنند و کاربر را از آن می‌خوانند، پس هرگز لازم نیست خودتان شناسه‌ی کاربر را به آن‌ها بدهید. این توکن شامل این‌هاست: #### تمدید تمدید توکن‌ها کار شما نیست. SDK access token را نگه می‌دارد و وقتی یک SDK ابرووا (یا کد شما) آن را بخواهد، اگر بیش از ۲ دقیقه از اعتبارش مانده باشد همان را برمی‌گرداند و در غیر این صورت تمدیدش می‌کند. چند درخواست‌کننده که هم‌زمان آن را بخواهند، در یک تمدید شریک می‌شوند. هر تمدید یک refresh token **جدید** برمی‌گرداند و قبلی را بازنشسته می‌کند. اگر یک refresh token بازنشسته دوباره استفاده شود، یعنی کسی آن را کپی کرده است: Abrova Auth فوراً کل آن نشست را پایان می‌دهد و آن را در گزارش فعالیت ثبت می‌کند. کاربر واقعی فقط دوباره وارد می‌شود؛ نسخه‌ی کپی‌شده از کار می‌افتد. تمدیدهای هم‌زمان از یک اپ به‌طور امن مدیریت می‌شوند. #### آفلاین و خطاهای شبکه خطای شبکه هرگز کاربر را خارج نمی‌کند. SDK نشست را نگه می‌دارد و در درخواست بعدی دوباره تلاش می‌کند. کاربر فقط وقتی خارج می‌شود که Abrova Auth بگوید نشست تمام شده است. ### پایان دادن به نشست وقتی نشستی روی سرور پایان می‌یابد، SDK روی آن دستگاه در تمدید بعدی متوجه می‌شود، نشست را پاک می‌کند و به listener وضعیت ورود شما خبر می‌دهد (کاربر `null` می‌شود). access tokenها با امضایشان بررسی می‌شوند، نه با جستجو. access tokenی که پیش از خروج از همه‌جا، غیرفعال‌سازی یا حذف صادر شده، در محصولات دیگر ابرووا تا زمان انقضا، حداکثر یک ساعت بعد، کار می‌کند. تمدید آن فوراً رد می‌شود، پس قابل تمدید نیست. ### در کنسول **Abrova Auth › کاربران** فهرست کاربران پروژه را همراه روش‌های ورود، زمان ساخت و آخرین فعالیت‌شان نشان می‌دهد. از همان‌جا می‌توانید کاربری را غیرفعال یا فعال کنید، او را از همه‌جا خارج کنید، یا حذفش کنید. - کاربران ناشناس و ارتقا: https://abrova.ir/docs/auth/upgrading کاربر ناشناس یک کاربر واقعی Abrova Auth است که اطلاعات ورود خودش را ندارد: فقط دستگاهی که آن را ساخته نشستش را نگه می‌دارد. اپ شما می‌تواند بلافاصله با همه‌ی محصولات ابرووا از آن استفاده کند و بعداً، بدون از دست رفتن چیزی، از کاربر بخواهد به‌طور کامل وارد شود. ### چرا با ناشناس شروع کنیم - **بدون دیوار ورود.** کاربر اول اپ شما را امتحان می‌کند. سندهای Sync، اتاق‌های چت و تخصیص‌های A/B test از همان ابتدا به یک شناسه‌ی کاربر پایدار تعلق دارند. - **قوانین امنیتی از اولین صفحه.** `auth.uid` برای کاربر ناشناس هم مقدار دارد، پس قوانینی مثل `data.ownerId === auth.uid` داده‌های او را از کاربران دیگر محافظت می‌کنند. - **آزمایش برای بازدیدکنندگانِ واردنشده.** Abrova A/B Testing برای هر کاربر به توکن کاربر نیاز دارد؛ کاربر ناشناس یکی دارد.

ساخت کاربر ناشناس

**ناشناس** را در **Abrova Auth › روش‌های ورود** روشن کنید، سپس: ```dart if (AbrovaAuth.instance.currentUser == null) { await AbrovaAuth.instance.signInAnonymously(); } ``` اول بررسی کنید کاربر فعلی وجود دارد یا نه. SDK نشست ذخیره‌شده را هنگام اجرا بازیابی می‌کند؛ صدا زدن `signInAnonymously()` در هر اجرا، هر بار یک کاربر جدید می‌سازد. [ورود ناشناس](/docs/auth/anonymous) را ببینید. وقتی کاربر آماده است حسابش را نگه دارد، یک روش را به کاربر ناشناسِ واردشده **وصل** کنید. شناسه‌ی کاربر تغییر نمی‌کند، پس هر چه زیر آن ذخیره شده مال خودش می‌ماند. ```dart try { await AbrovaAuth.instance.linkGoogle(idToken: idToken, rawNonce: rawNonce); // Same user id. currentUser.anonymous is now false. } on AbrovaProviderAlreadyLinkedException { // This Google account already belongs to another user. } ``` بعد از وصل کردن، کاربر می‌تواند روی هر دستگاهی با آن روش وارد شود و به همان حساب برسد. وصل کردن یک نشست تازه هم شروع می‌کند که SDK آن را در دست می‌گیرد.

وقتی حساب از قبل وجود دارد

اگر حساب Apple یا Google از قبل متعلق به کاربر دیگری از پروژه‌ی شما باشد، وصل کردن با `PROVIDER_ALREADY_LINKED` شکست می‌خورد (برای رمز عبور: `EMAIL_TAKEN`). هیچ چیز خودبه‌خود ادغام نمی‌شود: داده‌های دو کاربر هرگز بی‌خبر از شما یکی نمی‌شوند. کاری که بیشتر اپ‌ها می‌کنند: 1. به کاربر بگویید این حساب از قبل وجود دارد. 2. ورود به آن را پیشنهاد دهید. ورود معمولی (`signInWithGoogle`) را صدا بزنید که جای نشست ناشناس را می‌گیرد. 3. اگر کاربر ناشناس داده‌ی ارزشمندی داشت، پیش از جابه‌جایی آن را در اپ خودتان به کاربرِ واردشده منتقل کنید، یا کنارش بگذارید. صدا زدن یک روش ورود (نه روش link) وقتی کاربر ناشناسی وارد شده، دستگاه را به کاربر دیگر منتقل می‌کند. کاربر ناشناس حذف نمی‌شود، اما دیگر هیچ دستگاهی نشستش را ندارد، پس هرگز دوباره قابل دسترسی نیست. وقتی می‌خواهید شناسه بماند link کنید؛ وقتی حساب دیگر را می‌خواهید وارد شوید. ### سقف‌ها در برابر ربات‌ها ورود ناشناس به هیچ اطلاعاتی نیاز ندارد، پس راحت‌ترین روش برای سوءاستفاده است. Abrova Auth آن را از سه راه محدود می‌کند: - **برای هر آدرس دستگاه:** هجوم کاربران ناشناس جدید از یک آدرس کُند می‌شود. - **برای هر پروژه در روز:** تا ۲۰۰ کاربر ناشناس جدید در روز. می‌توانید سقف کمتری در **Abrova Auth › تنظیمات › کاربران ناشناس** بگذارید. - با عبور از سقف، `signInAnonymously()` با `RATE_LIMITED` شکست می‌خورد. کاربران موجود تأثیری نمی‌بینند. ### پاک‌سازی کاربران ناشناس رهاشده کاربر ناشناسی که هرگز برنمی‌گردد، بعد از چند روز بدون ورود یا تمدید نشست حذف می‌شود: به‌طور پیش‌فرض **۹۰ روز**، قابل تنظیم از ۷ تا ۳٬۶۵۰ در **Abrova Auth › تنظیمات › کاربران ناشناس**. کاربرانی که یک روش را وصل کرده‌اند هرگز این‌طور حذف نمی‌شوند. پاک‌سازی فقط کاربر را از Abrova Auth حذف می‌کند. سندهای Sync، پیام‌های Chat و داده‌های دیگری که زیر آن شناسه‌ی کاربر ذخیره شده‌اند سر جایشان می‌مانند. اگر لازم است، در پاک‌سازی خودتان آن‌ها را حذف کنید. - کاربر فعال ماهانه و سقف‌ها: https://abrova.ir/docs/auth/mau هزینه‌ی Abrova Auth بر اساس **کاربر فعال ماهانه** (MAU) است: تعداد کاربران متفاوتی که در یک ماه تقویمی وارد شده‌اند یا نشست‌شان را تمدید کرده‌اند. هر کاربر **ماهی یک بار** شمرده می‌شود، هر چند بار هم اپ را باز کند و روی هر چند دستگاه. ### چه چیزی شمرده می‌شود کاربر اولین باری که در یک ماه (به وقت UTC) یکی از این کارها را انجام دهد شمرده می‌شود: - با هر روشی وارد شود، از جمله ورود ناشناس و وصل کردن یک روش، یا - نشستی را تمدید کند (SDK این کار را در پس‌زمینه، تقریباً هر یک ساعت یک بار تا وقتی از اپ استفاده می‌شود، انجام می‌دهد). بقیه‌ی کارها رایگان است: خواندن کاربر فعلی، فراخوانی محصولات دیگر ابرووا با توکن، خروج، حذف حساب. کاربری که اپ را نصب کرده و در ماه بعد دیگر بازش نکرده، در آن ماه شمرده نمی‌شود. ### رایگان در هر ماه، سپس هزینه از کیف پول | | رایگان در هر ماه | بیشتر از آن | |---|---|---| | کاربر فعال ماهانه | ۱٬۰۰۰ | ۱۰٬۰۰۰ تومان برای هر ۱٬۰۰۰ کاربر، هزینه از کیف پول | | کاربر ناشناس جدید در روز | ۲۰۰ | سقف روزانه: در آن روز کاربر ناشناس جدیدی ساخته نمی‌شود | | روش‌های ورود | هر ۶ روش | شامل است | | گزارش فعالیت | شامل است | شامل است | پلن یا اشتراکی وجود ندارد. ابرووا از کیف پول تومانیِ پیش‌پرداخت برداشت می‌کند: هر وقت خواستید آن را شارژ می‌کنید و مصرفِ بیشتر از سهمیه‌ی رایگان از آن کم می‌شود. [صفحه‌ی قیمت‌ها](/pricing) را ببینید. ۱٬۰۰۰ کاربر فعال ماهانه رایگان در هر ماه. هر ۱٬۰۰۰ کاربر بیشتر، ۱۰٬۰۰۰ تومان هزینه از کیف پول. ### در سقف #### وقتی کیف پول موجودی دارد هیچ‌کس رد نمی‌شود. هزینه‌ی کاربرانِ بیشتر از ۱٬۰۰۰ کاربر رایگان، با نرخ ۱۰٬۰۰۰ تومان برای هر ۱٬۰۰۰، از کیف پول پرداخت می‌شود. شمارش ماه را در **Abrova Auth › نمای کلی** می‌بینید. #### وقتی کیف پول خالی است وقتی در این ماه ۱٬۰۰۰ کاربر فعال بوده‌اند و کیف پول نمی‌تواند هزینه‌ی بیشتر را بدهد، کاربرِ **جدیدِ** آن ماه نمی‌تواند وارد شود: فراخوانی با `MAU_LIMIT` شکست می‌خورد. - کاربرانی که این ماه شمرده شده‌اند به کار ادامه می‌دهند: مثل همیشه وارد می‌شوند و نشست‌شان را تمدید می‌کنند. - کاربری که رد شده نشست ذخیره‌شده‌اش را نگه می‌دارد. این نشست از اول ماه بعد، یا به محض اینکه کیف پول را شارژ کنید، دوباره کار می‌کند. - هیچ چیز حذف نمی‌شود: کاربران و تنظیمات‌تان همان‌طور می‌مانند. برای `MAU_LIMIT` پیام دوستانه‌ای نشان دهید («ورود فعلاً در دسترس نیست، لطفاً بعداً دوباره امتحان کنید»). کاربران‌تان نمی‌توانند مشکل را حل کنند؛ شما می‌توانید، با شارژ کیف پول. ### کاربر ناشناس جدید در روز جدا از MAU، هر پروژه می‌تواند روزانه تا **۲۰۰ کاربر ناشناس جدید** بسازد. این سقف جلوی ربات‌ها را می‌گیرد تا فهرست کاربران (و شمارش MAU) شما را پر نکنند. می‌توانید سقف کمتری در **Abrova Auth › تنظیمات › کاربران ناشناس** بگذارید. با عبور از سقف، `signInAnonymously()` با `RATE_LIMITED` و یک زمان تلاش دوباره شکست می‌خورد. کاربرانی که از قبل وجود دارند تأثیری نمی‌بینند و روش‌های ورود دیگر همچنان کار می‌کنند. ### شمارش درست - **در هر بار اجرا کاربر ناشناس جدید نسازید.** اول ببینید کاربری وارد شده یا نه؛ SDK نشست را هنگام اجرا بازیابی می‌کند. - **کاربران ناشناس رهاشده** بعد از ۹۰ روز بدون استفاده حذف می‌شوند (قابل تنظیم). در هر حال از ماه بعد از اینکه استفاده از اپ را کنار بگذارند، دیگر شمرده نمی‌شوند. - **برای هر نفر یک کاربر.** به جای ساختن کاربر دوم، یک روش ورود را به کاربر ناشناس وصل کنید. [کاربران ناشناس و ارتقا](/docs/auth/upgrading) را ببینید. کاربران فعال روزانه، کاربران جدید و تعداد کسانی که برمی‌گردند در [آمار استفاده](/docs/auth/usage-insights) هستند (برای هر پروژه جداگانه روشنش کنید). - آمار استفاده: https://abrova.ir/docs/auth/usage-insights Abrova Auth از قبل ورود و تمدید نشست هر کدام از کاربران شما را می‌بیند. **آمار استفاده** این را به اعداد روزانه تبدیل می‌کند: کاربران فعال، کاربران جدید و کاربران بازگشتی. لازم نیست چیزی به اپ اضافه کنید: نه SDK جدید، نه داده‌ی جدید از دستگاه، و هیچ راه‌اندازی‌ای جز یک کلید. ### روشن کردن آمار استفاده تا وقتی روشنش نکنید **خاموش** است؛ برای هر پروژه جداگانه در **Abrova Auth › تنظیمات › آمار استفاده** روشن می‌شود (مالکان و مدیران). شمارش از همان لحظه شروع می‌شود؛ چیزی از قبل پر نمی‌شود. **منطقه‌ی زمانی** که روزها بر اساس آن شمرده می‌شوند را انتخاب کنید (پیش‌فرض UTC؛ برای کاربران داخل ایران `Asia/Tehran`) تا «امروز» با بازار شما جور باشد. این یک تنظیم برای هر پروژه است و برای همه‌ی کسانی که آن را می‌بینند یکسان است. تغییر از روز بعد اعمال می‌شود؛ روزهای گذشته اعدادشان را نگه می‌دارند، و در روزِ تغییر ممکن است بعضی کاربران دو بار شمرده شوند. خاموش کردن، شمارش را متوقف می‌کند و اعداد استفاده‌ی پروژه را **حذف** می‌کند. ### چه چیزی می‌بینید در **Abrova Auth › نمای کلی › استفاده**: | | معنا | |---|---| | **فعال امروز** | تعداد کاربران متفاوتی که امروز فعال بوده‌اند | | **کاربران فعال روزانه** | تعداد کاربران متفاوتِ فعال در هر روز (۳۰، ۹۰ یا ۴۰۰ روز) | | **فعال هفتگی** | تعداد کاربران متفاوتِ فعال در این هفته (دوشنبه تا یکشنبه) | | **فعال ماهانه** | تعداد کاربران متفاوتِ فعال در این ماه تقویمی — همان عددی که بر اساسش هزینه می‌پردازید | | **ماندگاری** | میانگین کاربران فعال روزانه در ۳۰ روز گذشته ÷ فعال ماهانه («مردم در X درصد روزها از آن استفاده می‌کنند») | | **کاربران جدید** | کاربرانی که در هر روز ساخته شده‌اند | | **کاربران بازگشتی** | از کاربرانی که در یک روز ثبت‌نام کرده‌اند، چند نفر دقیقاً ۱، ۷ و ۳۰ روز بعد دوباره فعال بوده‌اند؛ به تفکیک هفته‌ی ثبت‌نام | | **به تفکیک پلتفرم** | کاربران فعال روزانه برای هر پلتفرم SDK: Android، iOS، وب، Flutter، React Native | | **به تفکیک روش ورود** | کاربران فعال روزانه برای هر روش: ناشناس، Apple، Google، رمز عبور، لینک یک‌بارمصرف، تلگرام | اعداد در حدود یک دقیقه به‌روز می‌شوند. کاربران فعال هفتگی و ماهانه مربوط به هفته‌ی جاری و ماه تقویمی جاری‌اند، نه ۷ یا ۳۰ روزِ اخیر. ### «فعال» یعنی چه کاربر در روزی فعال است که در آن روز **وارد شود** یا **نشستی را تمدید کند**. SDKها access token یک‌ساعته را وقتی کمتر از ۲ دقیقه از اعتبارش مانده و اپ شما (یا SDK دیگری از ابرووا) آن را می‌خواهد تمدید می‌کنند: در عمل وقتی اپ بعد از بیش از یک ساعت باز می‌شود، و تقریباً هر یک ساعت یک بار در حین استفاده. - درخواستی که رد شود شمرده نمی‌شود: سقف کاربر ماهانه با کیف پول خالی (`MAU_LIMIT`)، کاربر غیرفعال، نشست منقضی یا پایان‌یافته. - حذف یک کاربر، یا حذف کاربران ناشناس بی‌استفاده، روزهای گذشته را تغییر نمی‌دهد. **کمبود شناخته‌شده.** کاربری که اپ را آخر شب و دوباره کمی بعد از نیمه‌شب، در همان یک ساعت، باز می‌کند، در روز دوم دیده نمی‌شود: توکنش هنوز معتبر است، پس چیزی به Abrova Auth نمی‌رسد. به همین دلیل اعداد روزانه ممکن است کمی کمتر باشند، هرگز بیشتر. ### چه چیزی ذخیره می‌شود شمارش، نه ردیابی. Abrova Auth فقط ذخیره می‌کند **چند نفر** از کاربران شما در هر روز فعال بوده‌اند — مثلاً «۳۱۲ کاربر در ۲۴ اکتبر» — و هرگز فهرستی از روزها یا ساعت‌هایی که یک شخص از اپ شما استفاده کرده نگه نمی‌دارد. - هیچ داده‌ی جدیدی از دستگاه خارج نمی‌شود؛ فقط درخواست‌هایی که Abrova Auth از قبل دریافت می‌کند شمرده می‌شوند. - هیچ آدرس IP، ایمیل، شماره تلفن، توکن یا شناسه‌ی کاربری بخشی از شمارش نیست. - اعداد فقط برای شماست: بدون شخص ثالث، بدون تبلیغات، بدون پروفایل‌سازی. - شمارش‌ها ۴۰۰ روز نگه داشته می‌شوند. می‌توانید آن را در سیاست حریم خصوصی خودتان توضیح دهید، مثلاً: *«تعداد افرادی را که هر روز از اپ استفاده می‌کنند با یک شناسه‌ی ناشناس می‌شماریم؛ بدون نام، بدون ایمیل، بدون ردیابی.»* ### رایگان و با موجودی کیف پول آمار استفاده هزینه‌ی اضافه‌ای ندارد. اینکه کنسول چه مقدار نشان دهد بستگی دارد به اینکه پروژه‌ی شما فقط از سهمیه‌ی رایگان ماهانه استفاده می‌کند یا به‌صورت پرداخت به‌اندازه‌ی مصرف از موجودی کیف پول هزینه می‌کند: | | فقط سهمیه‌ی رایگان | با موجودی کیف پول (پرداخت به‌اندازه‌ی مصرف) | |---|---|---| | فعال امروز، کاربران فعال روزانه | ۳۰ روز | ۴۰۰ روز | | فعال هفتگی / ماهانه، ماندگاری، کاربران جدید | بله | بله | | کاربران بازگشتی (روز ۱ / ۷ / ۳۰) | ۴ هفته‌ی اخیر | ۴۰۰ روز | | به تفکیک پلتفرم / روش ورود | — | بله | | API آمار | — | بله | شمارش‌ها همیشه ۴۰۰ روز نگه داشته می‌شوند؛ فقط چیزی که نمایش داده می‌شود تغییر می‌کند، پس با شارژ کیف پول کل تاریخچه‌تان فوراً نمایش داده می‌شود. ### API آمار برای داشبوردها و اسکریپت‌های خودتان، وقتی پروژه‌ی شما به‌صورت پرداخت به‌اندازه‌ی مصرف از موجودی کیف پول هزینه می‌کند. آن را **از سرور خودتان** با کلید API و کلید سرور صدا بزنید، هرگز از اپ یا مرورگر: کلید سرور نباید در کد سمت کلاینت قرار بگیرد. اپی که فقط کاربران را وارد می‌کند به هیچ‌کدام از این‌ها نیاز ندارد — کنسول همین اعداد را نشان می‌دهد. ```bash curl "https://auth-users.abrova.ir/v1/projects/YOUR_PROJECT_ID/insights?from=2026-10-01&to=2026-10-24" \ -H "x-api-key: $ABROVA_API_KEY" \ -H "x-server-secret: $ABROVA_SERVER_SECRET" ``` ```json { "enabled": true, "timeZone": "Asia/Tehran", "today": "2026-10-24", "activeToday": 312, "weeklyActive": 1104, "monthlyActive": 1840, "stickiness": 0.17, "days": [ { "day": "2026-10-24", "active": 312, "new": 41, "byPlatform": { "android": 201, "ios": 111 }, "byMethod": { "anonymous": 290, "google": 22 } } ], "returning": [ { "cohort": "2026-10-17", "size": 52, "day1": 21, "day7": 12, "day30": null } ] } ``` - پارامترهای `from` و `to` روزهایی در منطقه‌ی زمانی پروژه‌اند، با قالب `YYYY-MM-DD`. بدون آن‌ها کل بازه‌ای را که می‌توانید ببینید می‌گیرید؛ بازه‌ای که از آن بیرون بزند به همان کوتاه می‌شود. - مقادیر `day1`، `day7` و `day30` تا وقتی cohort به اندازه‌ی کافی قدیمی نشده `null` هستند. - فهرست IPهای مجاز کلید سرور پروژه‌ی شما اعمال می‌شود. | وضعیت | `code` | معنا | |---|---|---| | 401 | `UNAUTHENTICATED` | کلید API یا کلید سرور نیست یا اشتباه است، یا متعلق به پروژه‌ی دیگری است | | 403 | `LIMIT_REACHED` | API به موجودی کیف پول نیاز دارد (پرداخت به‌اندازه‌ی مصرف) | | 429 | `RATE_LIMITED` | بیش از ۶۰ درخواست در دقیقه از یک آدرس | ### چه چیزی شامل نیست رویدادها، قیف‌ها (funnel)، صفحه‌ها و ویژگی‌های سفارشی بخشی از Abrova Auth نیستند: این سرویس کاربران را می‌شمارد، نه کارهایی که انجام می‌دهند. چیزی مثل «آیا کاربران مرحله‌ی ۳ را تمام کردند» را در داده‌های خودتان نگه دارید، مثلاً یک سند Abrova Sync برای هر کاربر. - امنیت و داده: https://abrova.ir/docs/auth/security Abrova Auth تا جای ممکن کمترین اطلاعات را از کاربران شما نگه می‌دارد، هر چه را که مجبور است نگه دارد رمزگذاری می‌کند، و اطلاعات محرمانه (رمزهای عبور، توکن‌ها، کدها) را فقط به شکلی ذخیره می‌کند که نتوان آن را به مقدار اصلی برگرداند. ### چه چیزی ذخیره می‌شود **ذخیره‌ی اطلاعات پروفایل** را در **Abrova Auth › تنظیمات** فقط وقتی روشن کنید که اپ شما به ایمیل یا نامی که Apple و Google برمی‌گردانند نیاز دارد. وقتی خاموش است، این کاربران فقط با شناسه‌شان و هش شناسه‌ی حساب ارائه‌دهنده شناخته می‌شوند. ### رمزگذاری - **در حالت ذخیره:** داده‌های شخصی (آدرس‌های ایمیل، نام‌ها و شماره تلفن‌ها) رمزگذاری‌شده ذخیره می‌شوند. - **در حین انتقال:** همه‌ی درخواست‌ها از HTTPS استفاده می‌کنند. - **رمزهای عبور:** به‌صورت هش یک‌طرفه ذخیره می‌شوند و هرگز به هیچ شکل خوانایی ذخیره یا log نمی‌شوند. ورود، چه ایمیل حساب داشته باشد چه نه، همان مدت زمان را می‌برد. - **اطلاعات محرمانه‌ی پروژه‌ی شما** (bot token تلگرام و کلید Apple برای حذف حساب) رمزگذاری‌شده ذخیره می‌شوند و بعد از ذخیره دیگر نمایش داده نمی‌شوند. - **logها** هرگز توکن، رمز عبور، آدرس ایمیل، شماره تلفن یا آدرس IP را به‌صورت خوانا ندارند. ### جداسازی پروژه‌ها پروژه‌ی هر درخواست از کلید API آن تعیین می‌شود، هرگز از چیزی که اپ در درخواست می‌فرستد. کاربران، نشست‌ها و تنظیمات یک پروژه از پروژه‌ی دیگر قابل خواندن یا استفاده نیستند. هر access token نام پروژه‌اش را دارد و همه‌ی محصولات ابرووا توکنِ پروژه‌ی دیگر را رد می‌کنند. ### محافظت در برابر حمله‌ها با عبور از سقف، فراخوانی با `RATE_LIMITED` شکست می‌خورد و می‌گوید کی دوباره امتحان کنید (`retryAfterSec`). ### گزارش فعالیت برای همه‌ی پروژه‌ها شامل است: **Abrova Auth › فعالیت** رویدادهای امنیتی پروژه‌ی شما را نشان می‌دهد: ساخت کاربران، ورودها و ورودهای ناموفق، روش‌های وصل‌شده، بازیابی رمز عبور، خروج از همه‌جا، استفاده‌ی دوباره از refresh token، غیرفعال، فعال یا حذف شدن کاربران، و تغییر تنظیمات. رویدادها آدرس مبدأ را به‌صورت هش‌شده نشان می‌دهند، تا بدون دیدن آدرس بفهمید دو رویداد از یک جا آمده‌اند. ### حذف حساب Apple از اپ‌هایی که امکان ساخت حساب دارند می‌خواهد اجازه دهند کاربران حساب‌شان را داخل اپ حذف کنند. `deleteAccount()` را صدا بزنید: - کاربر، همه‌ی نشست‌ها و روش‌های وصل‌شده‌اش، و هر چه Abrova Auth درباره‌ی او ذخیره کرده حذف می‌شود. این کار قابل بازگشت نیست. - اگر کاربر با Apple وارد شده و شما **کلید حذف حساب** (Team ID، Key ID و کلید خصوصی `.p8` در **Abrova Auth › روش‌های ورود › Apple**) را تنظیم کرده باشید، Abrova Auth همان‌طور که Apple می‌خواهد، ورود با Apple این کاربر را برای اپ شما هم لغو می‌کند. وقتی کاربر با Apple وارد می‌شود `authorizationCode` مربوط به Apple را بفرستید، وگرنه این مرحله چیزی برای لغو ندارد. - داده‌ها در محصولات دیگر ابرووا (سندهای Sync، پیام‌های Chat، فایل‌های Storage) حذف نمی‌شوند. پیش از صدا زدن `deleteAccount()`، تا وقتی توکن کاربر هنوز کار می‌کند، آن‌ها را از داخل اپ حذف کنید. همچنین می‌توانید کاربر را از **Abrova Auth › کاربران** در کنسول حذف کنید. - استفاده با محصولات دیگر ابرووا: https://abrova.ir/docs/auth/other-products Abrova Sync، Chat، Push، A/B Testing و Storage کاربر اپ شما را با یک **توکن کاربر ابرووا**ی امضاشده می‌شناسند. بدون Abrova Auth، سرور خودتان این توکن را با کلید سرور می‌سازد. با Abrova Auth، توکن از قبل در SDK Auth هست: **token provider** آن را به SDKهای دیگر بدهید تا به‌عنوان کاربرِ واردشده عمل کنند. Abrova Sync، Chat، Push، Storage و A/B Testing به‌زودی می‌آیند. این صفحه نشان می‌دهد وقتی راه بیفتند کاربر Auth را چطور می‌گیرند؛ فعلاً قابل استفاده نیستند. امروز کاربرِ واردشده با [ابرووا تریس](#abrova-trace-today) کار می‌کند. ### ابرووا تریس، از امروز ابرووا تریس فعال است. شناسه‌ی کاربر Abrova Auth را به آن بدهید تا خطاها و کرش‌ها نشان دهند برای کدام کاربر رخ داده‌اند. آن را بعد از ورود تنظیم کنید و بعد از خروج پاک کنید: ```dart // Flutter AbrovaTrace.setUserId(AbrovaAuth.instance.currentUser!.id); ``` ```swift // iOS AbrovaTrace.shared.setUserId(AbrovaAuth.shared.currentUserId) // nil after sign-out ``` ```kotlin // Android AbrovaTrace.setUserId(AbrovaAuth.shared.currentUserId) // null after sign-out ``` تریس فقط شناسه‌ی کاربر را می‌گیرد؛ به توکن نیازی ندارد. [SDK Flutter تریس](/docs/trace/flutter-sdk)، [SDK iOS](/docs/trace/ios-sdk) و [SDK Android](/docs/trace/android-sdk) را ببینید. ### چه چیزی به دست می‌آورید - **قوانین امنیتی Sync بدون سرور کار می‌کنند.** `auth.uid` همان شناسه‌ی کاربر Abrova Auth (`ru_...`) است، پس `data.ownerId === auth.uid` از سندهای هر کاربر محافظت می‌کند. [قوانین امنیتی Sync](/docs/sync/rules) را ببینید. - **Chat می‌داند چه کسی صحبت می‌کند.** پیام‌ها به نام کاربرِ توکن فرستاده می‌شوند؛ هیچ‌کس نمی‌تواند به جای دیگری بنویسد. - **قوانین دسترسی Storage کاربر را می‌بینند.** قوانینی مثل `users/{userId}/**` به هر کاربر اجازه می‌دهند فقط فایل‌های خودش را بخواند و بنویسد. [قوانین دسترسی Storage](/docs/storage/bucket-policies) را ببینید. - **A/B Testing و Push** رویدادها، variantها و پیام‌های inbox را به کاربر وصل می‌کنند، از جمله کاربران ناشناسی که هنوز به‌طور کامل وارد نشده‌اند. - **چیز اضافه‌ای برای وصل کردن نیست.** SDKهای دیگر یک token provider می‌گیرند. توکن همان توکنی است که سرور خودتان می‌ساخت. ### توکن چطور جریان پیدا می‌کند وقتی کسی وارد نشده، provider توکنی برنمی‌گرداند (یا برای Chat خطا می‌دهد)، و هر SDK همان‌طور که مستنداتش می‌گوید منتظر کاربر می‌ماند. ### دادن token provider این محصولات به‌زودی می‌آیند؛ فراخوانی‌های زیر نشان می‌دهند توکن را چطور خواهند گرفت. هر جا SDKی شناسه‌ی کاربر خواست (`userId` در Chat، `setUserId` در A/B Testing)، شناسه‌ی کاربر را از Abrova Auth بدهید تا با توکن جور باشد. #### Flutter ```dart final auth = AbrovaAuth.instance; // Abrova Sync await AbrovaSync.init(AbrovaSyncConfig( apiKey: 'ab_live_your_api_key', tokenProvider: auth.tokenProvider, )); // Abrova Push await AbrovaPush.init(AbrovaPushConfig( apiKey: 'ab_live_your_api_key', tokenProvider: auth.tokenProvider, )); // Abrova A/B Testing await AbrovaAbTesting.init(AbrovaAbTestingConfig( apiKey: 'ab_live_your_api_key', tokenProvider: auth.tokenProvider, )); // Abrova Storage final storage = AbrovaStorage( apiKey: 'ab_live_your_api_key', tokenProvider: auth.tokenProvider, ); // Abrova Chat: its provider must return a token, so use chatTokenProvider final chat = AbrovaChatClient(AbrovaChatConfig( apiKey: 'ab_live_your_api_key', userId: auth.currentUser!.id, tokenProvider: auth.chatTokenProvider, )); ``` وقتی کسی وارد نشده، `tokenProvider` مقدار `null` برمی‌گرداند. `chatTokenProvider` به جای آن خطای `UNAUTHENTICATED` می‌دهد؛ کلاینت Chat را بعد از ورود بسازید. #### iOS ```swift let auth = AbrovaAuth.shared // Abrova Sync sync.userTokens.provider = auth.tokenProvider // Abrova Push AbrovaPushConfig(apiKey: key, tokenProvider: auth.tokenProvider) // Abrova Storage let storage = AbrovaStorage(apiKey: key, userTokenProvider: auth.tokenProvider) // Abrova Chat: create the client after sign-in AbrovaChatConfig(apiKey: key, userId: auth.currentUserId!, tokenProvider: auth.strictTokenProvider) // Abrova A/B Testing takes a completion handler AbrovaAbTestingConfig(apiKey: key, tokenProvider: { completion in Task { do { completion(.success(try await auth.strictTokenProvider())) } catch { completion(.failure(error)) } } }) ``` وقتی کسی وارد نشده، `tokenProvider` مقدار `nil` برمی‌گرداند؛ `strictTokenProvider` خطا می‌دهد. #### Android ```kotlin val auth = AbrovaAuth.shared // Abrova Sync: its provider is called off the main thread AbrovaSyncConfig.builder(apiKey).userTokenProvider(auth.blockingTokenProvider).build() // Abrova Push AbrovaPushConfig(apiKey = apiKey, tokenProvider = auth.tokenProvider) // Abrova Storage: also called off the main thread AbrovaStorage.builder(apiKey).userTokenProvider(auth.blockingTokenProvider).build() // Abrova A/B Testing: also called off the main thread AbrovaAbTestingConfig(apiKey = apiKey, tokenProvider = AbrovaTokenProvider { auth.blockingTokenProvider() }) // Abrova Chat: create the client after sign-in AbrovaChatConfig(apiKey = apiKey, userId = auth.currentUserId!!, tokenProvider = auth.strictTokenProvider) ``` `tokenProvider` یک تابع suspend است که وقتی کسی وارد نشده `null` برمی‌گرداند؛ `strictTokenProvider` به جای آن خطا می‌دهد؛ `blockingTokenProvider` thread فراخواننده را block می‌کند و هرگز نباید روی main thread اجرا شود. #### Web ```typescript import { createAbrovaAuth } from '@abrova/auth-web'; import { AbrovaSync } from '@abrova/sync-web'; import { AbrovaStorage } from '@abrova/storage-web'; const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key' }); // Abrova Sync, Push, A/B Testing and Storage: null while signed out const sync = new AbrovaSync({ apiKey: 'ab_live_your_api_key', tokenProvider: () => auth.getToken() }); const storage = new AbrovaStorage({ apiKey: 'ab_live_your_api_key', tokenProvider: () => auth.getToken() }); // Abrova Chat: auth.tokenProvider rejects with UNAUTHENTICATED while signed out const chat = new AbrovaChatClient({ apiKey: 'ab_live_your_api_key', userId: auth.currentUserId!, tokenProvider: auth.tokenProvider, }); ``` #### React Native ```typescript import AbrovaSync from '@abrova/sync-react-native'; import { AbrovaStorage } from '@abrova/storage-react-native'; // Abrova Sync, Push, A/B Testing and Storage: null while signed out await AbrovaSync.init({ apiKey: 'ab_live_your_api_key', tokenProvider: () => auth.getToken() }); const storage = new AbrovaStorage({ apiKey: 'ab_live_your_api_key', tokenProvider: () => auth.getToken() }); // Abrova Chat: create the client after sign-in const chat = new AbrovaChatClient({ apiKey: 'ab_live_your_api_key', userId: auth.currentUserId!, tokenProvider: auth.tokenProvider, }); ``` ### وقتی کاربر وارد یا خارج می‌شود SDKهای دیگر توکنی را که دارند cache می‌کنند. بعد از ورود، خروج یا جابه‌جایی به کاربر دیگر، به آن‌ها بگویید دوباره از provider بخواهند: به وضعیت ورود SDK Auth (`authStateChanges`، `onAuthStateChanged`، `addAuthStateListener` یا `authState`) گوش دهید و این فراخوانی‌ها را همان‌جا انجام دهید تا همه‌ی محصولات کاربر را دنبال کنند. ### نوشتن قوانین امنیتی برای کاربر شناسه‌ی کاربر در توکن، همان `auth.uid` در قوانین Abrova Sync است. آن را در سندهایی که مال کاربرند ذخیره کنید: ```json { "rules": { "notes": { ".read": "data.ownerId === auth.uid", ".write": "newData.ownerId === auth.uid" } } } ``` ```dart await AbrovaSync.database('my-app').collection('notes').add({ 'ownerId': AbrovaAuth.instance.currentUser!.id, 'text': 'Hello', }); ``` سپس **Require signed user tokens** را برای Sync در کنسول روشن کنید تا فقط توکن امضاشده بتواند `auth.uid` را تعیین کند. [قوانین امنیتی Sync](/docs/sync/rules) را ببینید. ### همراه با سرور خودتان همچنان می‌توانید روی سرور خودتان با کلید سرور (`POST https://auth.abrova.ir/users/token`) برای شناسه‌های کاربرِ خودتان توکن بسازید. هر دو نوع توکن در همه‌ی محصولات کار می‌کنند. برای هر اپ یک منبع شناسه‌ی کاربر نگه دارید تا یک شخص دو کاربر نشود. - کدهای خطا: https://abrova.ir/docs/auth/error-codes هر فراخوانیِ ناموفق یک `code` ثابت دارد. پیام‌هایی که به کاربران نشان می‌دهید را بر اساس همین کد و به زبان خودشان بسازید. `message` همراه آن یک راهنمای انگلیسی برای توسعه‌دهنده است: هرگز کلید محرمانه یا آدرس ایمیل ندارد و ممکن است تغییر کند. ### خطاها چطور می‌رسند با عبور از سقف درخواست، خطا مدت انتظار را هم دارد: `retryAfterSec` (iOS، Android، Web، React Native) یا `retryAfter` (Flutter). ### کدهای Abrova Auth ### کدهایی که SDKها اضافه می‌کنند این کدها از SDK می‌آیند، نه از Abrova Auth: یا پاسخی نیامده، یا مشکل روی دستگاه است. ### خطاهایی که نشست را پایان می‌دهند وقتی تمدید نشست با `SESSION_EXPIRED`، `SESSION_REVOKED`، `TOKEN_INVALID` یا `USER_DISABLED` شکست بخورد، SDK نشست را از روی دستگاه پاک می‌کند و listener وضعیت ورود شما `null` می‌گیرد. همان‌جا کاربر را به صفحه‌ی ورود ببرید. هر خطای دیگری، از جمله `NETWORK_ERROR`، نشست را حفظ می‌کند. ## Abrova Push (coming soon) - شروع سریع: https://abrova.ir/docs/push/quick-start ### ۱ · نصب پس از عرضه‌ی Abrova Push در دسترس است. SDK مثل همه‌ی SDKهای ابرووا از Abrova Center نصب می‌شود. ### ۲ · ارسال نوتیفیکیشن آزمایشی از کنسول یا از سرور با کلید سرور. قالب‌ها متغیر و نسخه‌ی فارسی / انگلیسی یک پیام را پشتیبانی می‌کنند. ### ۳ · اندروید بدون سرویس‌های گوگل Abrova Push به گوشی‌های بدون Google Play services، از جمله هواوی، می‌رسد. پل اختیاری FCM هم جایی که Firebase در دسترس است کار می‌کند. - پیکربندی: https://abrova.ir/docs/push/configuration All available configuration options for the AbrovaPush SDK. ### Configuration Options Call `register()` on every app launch. It keeps the device's token, app version and last-seen time current on the server. Log verbosity is set at runtime with `setLogLevel()` (see below). ### Full Configuration Example ```dart await AbrovaPush.init(AbrovaPushConfig( // Required - only apiKey needed, server config is auto-fetched apiKey: 'ab_live_your_api_key', // iOS Options usePushKit: false, // Enable only for apps with calling features appGroup: 'group.com.yourcompany.yourapp', // Android Options notificationIcon: 'ic_notification', showServiceNotification: true, // Optional showNotificationInForeground: true, autoConnect: true, )); ``` ### Log Levels
Level Description
none No logging output
error Only error messages
warning Errors and warnings
info Errors, warnings, and info messages
debug All messages (recommended for development)
verbose Everything including stack traces
```dart import 'package:flutter/foundation.dart'; // Set log level based on build mode if (kReleaseMode) { await AbrovaPush.setLogLevel(AbrovaPushLogLevel.error); } else { await AbrovaPush.setLogLevel(AbrovaPushLogLevel.debug); } ``` ### Web The Web SDK requires only `apiKey`. Connection configuration is automatically fetched from the server; the realtime connection runs over TLS on port 443. #### Web SDK Full Example ```typescript import AbrovaPush, { AbrovaPushLogLevel } from '@abrova/push-web'; const abrovaPush = new AbrovaPush({ // Required apiKey: 'ab_live_your_api_key', // Optional - defaults shown serviceWorkerPath: '/abrova-push-sw.js', autoRegisterServiceWorker: true, maxReconnectAttempts: 0, logLevel: AbrovaPushLogLevel.ERROR, appVersion: '2.0.0', autoRefresh: true, }); // Change log level at runtime abrovaPush.setLogLevel(AbrovaPushLogLevel.ERROR); ``` The Web SDK automatically fetches connection credentials and VAPID keys from the server using your API key. You don't need to configure these manually. - دستیارهای هوش مصنوعی: https://abrova.ir/docs/push/ai-assistants Abrova does not have a docs server for AI assistants yet. This page describes how it will work and what you can use today. When it is ready, you connect your AI assistant (any tool that supports MCP, the open standard AI tools use to connect to outside sources) once. Then, when you ask it to add Abrova Push to your project, it looks up the current SDK version, install lines and setup steps from these docs instead of answering from memory. ### What works today - **Copy page for AI.** Every docs page has a **Copy page for AI** link. Paste the page into your assistant's chat before you ask it to write code. - **Plain-text docs.** Give your assistant the address `https://abrova.ir/docs/llms.txt`. It lists every docs page as plain text that assistants read well. Tell your assistant to follow the page you gave it, to install the SDK from Abrova Center as the page shows, and to use the placeholder keys from the docs. ### What it will do The docs server will have read-only tools. Your assistant chooses them by itself; you do not call them by hand. Examples of what you will be able to ask: - **"Add Abrova Push to my Flutter app and open the order screen when a notification is tapped."** It would answer from the [Flutter SDK](/docs/push/flutter) and [Callbacks](/docs/push/callbacks) pages. - **"Send an order-shipped notification from my Node.js server to every device of a user."** It would answer from the [Node.js SDK](/docs/push/nodejs) and [Send push](/docs/push/send) pages. - **"Show a banner in my Android app the first time a user opens it."** It would answer from [In-App Messages](/docs/push/in-app). - **"Deliver to Android phones without Google services, and also use Firebase where it exists."** It would answer from [Android SDK](/docs/push/android) and [Android FCM bridge](/docs/push/android-fcm). - **"Which Abrova key goes in the app and which one stays on the server?"** It would answer from the [Node.js guide](/docs/push/nodejs-guide). ### Privacy - **What it reads:** the public Abrova docs, the current SDK versions, and the words your assistant sends (the search words, the page it wants, the product and platform names). - **What it never sees:** your code, your files, your Abrova project, your keys or your users. The tools have no input for them, and they cannot change any setting. Never paste a key into a chat with an AI assistant. Your assistant writes the placeholder from the docs (`ab_live_your_api_key`); you put the real value in your project's configuration yourself. The server secret (`ab_srv_...`) belongs only on your server, in an environment variable. More about AI assistants and Abrova: [AI assistants](/ai-assistants). Questions? [Contact us](/contact). - iOS: https://abrova.ir/docs/push/ios

Installation

AbrovaPush installs with Swift Package Manager: 1. In Xcode: **File → Add Package Dependencies** → enter the repository URL: ``` https://github.com/Abrova-co/abrova-push-ios-sdk.git ``` 2. Choose version `0.1.16` or later and add the `AbrovaPush` library to your app target. Or, in a `Package.swift`: ```swift dependencies: [ .package(url: "https://github.com/Abrova-co/abrova-push-ios-sdk.git", from: "0.1.16") ] ``` Import the module as `AbrovaPush`: ```swift import AbrovaPush ``` #### Required Capabilities In your Xcode project, enable: 1. **Push Notifications** 2. **Background Modes**: - Voice over IP - Background fetch - Remote notifications #### APNs Key Background delivery (APNs, and VoIP if you enable it) uses your project's APNs auth key — the same `.p8` key covers both regular and VoIP pushes: 1. Apple Developer Portal → **Certificates, Identifiers & Profiles → Keys** 2. Create a key with **Apple Push Notifications service (APNs)** enabled and download the `.p8` file 3. In Abrova Console → **Abrova Push → Settings → iOS Push (APNs) Configuration**, enter the Key ID, Team ID and paste the `.p8` key content

Quick Start

Initialize AbrovaPush in your `AppDelegate`: ```swift import UIKit import AbrovaPush @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Only apiKey is required — connection settings are fetched // automatically during registration. let config = AbrovaPushConfig(apiKey: "ab_live_your_api_key") AbrovaPush.shared.initialize(config: config) AbrovaPush.shared.delegate = self return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // Forward the APNs token so the server can reach the device in background. AbrovaPush.shared.setAPNsToken(deviceToken) } func applicationDidBecomeActive(_ application: UIApplication) { // Open the Abrova real-time connection while the app is in foreground. AbrovaPush.shared.connect() } func applicationWillResignActive(_ application: UIApplication) { // Close the real-time connection when going to background. // VoIP pushes will still wake the app. AbrovaPush.shared.disconnect() } } ```

Register the Device

```swift import UIKit import AbrovaPush class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Register on every launch: it keeps the device's token, app version // and last-seen time current on the server. AbrovaPush.shared.register( userId: "user_123", metadata: [ "tier": "premium" ] ) } } ```

Implement the Delegate

```swift extension AppDelegate: AbrovaPushDelegate { func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveMessage message: AbrovaPushMessage) { // New push notification — message.title, message.body, message.data } func abrovaPush(_ abrovaPush: AbrovaPush, didChangeConnectionState connected: Bool) { // Real-time connection state changed } func abrovaPush(_ abrovaPush: AbrovaPush, didRegisterWithDeviceId deviceId: String) { // Device registered with the Abrova backend } func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveVoIPToken token: String) { // VoIP token for backend delivery } func abrovaPush(_ abrovaPush: AbrovaPush, didFailWithError error: Error) { // Connection or registration error } } ```

How Push Delivery Works

``` ┌────────────────────────────────────────────────────────────┐ │ App State │ ├─────────────────┬─────────────────┬────────────────────────┤ │ Foreground │ Background │ Killed/Terminated │ ├─────────────────┼─────────────────┼────────────────────────┤ │ Abrova │ APNs │ APNs │ │ (real-time) │ (VoIP optional) │ (VoIP optional) │ └─────────────────┴─────────────────┴────────────────────────┘ ``` 1. **Foreground**: the Abrova real-time connection delivers messages instantly with no APNs latency. 2. **Background / Killed**: APNs delivers the notification. With `usePushKit` enabled, messages whose `data.type` is `"voip_call"` go out as VoIP pushes instead (calling apps only, see step 8). VoIP push is for calling apps. Apple requires every VoIP push to report an incoming call through CallKit, so enable `usePushKit` only if your app makes calls.

Topics

Subscribe users to topics so you can broadcast notifications to interest groups: ```swift AbrovaPush.shared.subscribeTopic("news") // Later, when the user opts out AbrovaPush.shared.unsubscribeTopic("news") ```

User Identification

```swift // Set user ID after login AbrovaPush.shared.setUserId("user_123") // Clear user ID after logout — also detaches the user on the server, // so this device stops receiving the previous user's notifications AbrovaPush.shared.clearUserId() ```

Backend VoIP Push Format

Abrova sends a VoIP push (instead of a regular APNs push) only when the message's `data.type` is `"voip_call"` and the device registered a VoIP token (`usePushKit: true`). The VoIP payload Abrova builds has this shape: ```json { "title": "New Message", "body": "You have a new message", "data": { "type": "voip_call", "screen": "chat", "chatId": "123" } } ``` The `data` dictionary is delivered verbatim to `didReceiveMessage` so you can drive in-app navigation.

Configuration Options

```swift let config = AbrovaPushConfig( apiKey: "ab_live_your_api_key", usePushKit: false, useAPNs: true, showNotificationInForeground: false, autoConnect: true, appGroup: "group.com.yourcompany.yourapp", autoRefresh: true ) ``` Connection settings are fetched automatically. There is nothing to configure. You can also build the config fluently with `AbrovaPushConfig.builder(apiKey:)`, for example `.usePushKit(false).autoRefresh(true).appGroup("group.com.yourcompany.yourapp").build()`. The installed SDK version is available as `AbrovaPush.sdkVersion`.

Delivery Confirmation (Notification Service Extension)

**Optional.** Set this up only if you want to know that background notifications actually *arrived*. Everything else in the SDK works without it. APNs tells the server it *accepted* a notification for delivery — it never confirms one reached the device. The SDK confirms delivery itself for messages the app sees: iOS does not run your app when a notification arrives in the background, so without an extension those deliveries stay `sent`. A Notification Service Extension is the only way iOS lets an app observe a real arrival: the system runs the extension before the notification is displayed, even when your app is not running. Each message is confirmed once — the app and the extension share the list of confirmed messages through the App Group. If you handle silent pushes, forward them too: ```swift func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { AbrovaPush.shared.handleRemoteNotification(userInfo: userInfo) completionHandler(.noData) } ``` An extension is a separate process, so it must be its own target — Apple provides no alternative. Every push SDK that reports iOS delivery works this way. Skip this section entirely if you do not need delivery confirmation. #### 1. Add the extension target In Xcode: **File → New → Target → Notification Service Extension**. When Xcode asks **"Activate scheme?"**, choose **Don't Activate** so your app stays the active scheme. Then set the extension's **iOS Deployment Target** to match your app. Xcode defaults new targets to the latest iOS version, which would drop support for every older device. #### 2. Enable the same App Group on both targets Select each target → **Signing & Capabilities → + Capability → App Groups**, and add the *same* group to **both** your app and the extension, for example `group.com.yourcompany.yourapp`. The extension runs in its own process and cannot read your app's storage. The App Group is the shared container the SDK uses to hand the device identity to the extension. #### 3. Add the SDK to the extension The extension needs the SDK too. In Xcode, select the extension target → **General → Frameworks and Libraries → +** and add the `AbrovaPush` library from the package you already added. The extension only uses `AbrovaPushServiceExtension`, which never touches `UIApplication`. #### 4. Pass the App Group to the SDK ```swift AbrovaPush.shared.initialize( config: AbrovaPushConfig( apiKey: "ab_live_your_api_key", appGroup: "group.com.yourcompany.yourapp" ) ) ``` #### 5. Report delivery from the extension Replace the generated `NotificationService.swift` with: ```swift import UserNotifications import AbrovaPush class NotificationService: UNNotificationServiceExtension { private static let appGroup = "group.com.yourcompany.yourapp" private static let apiKey = "ab_live_your_api_key" var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { self.contentHandler = contentHandler bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent AbrovaPushServiceExtension.didReceive( request, apiKey: NotificationService.apiKey, appGroup: NotificationService.appGroup ) contentHandler(bestAttemptContent ?? request.content) } override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } } ``` The report is fire-and-forget and never delays `contentHandler`. Extensions get a short execution budget, so a missed report costs a delivery statistic — never the notification itself. Xcode's generated extension appends `[modified]` to every notification title. If you leave it in, your users will see it. #### Verifying it works Send a notification and check the receipt for that device: it should move from `sent` to `delivered`. See [Delivery Receipts](/docs/push/receipts). #### Troubleshooting `', 'Build phase ordering. In the app target, move `Embed Foundation Extensions` before any script phase that outputs the `.app`.'], ['Extension never runs', '`Embed in Application` was not set when the target was created, so the extension is not inside the build.'], ]} /> ### API Reference Core methods of `AbrovaPush.shared` and supporting types (the SDK also exposes in-app message, inbox and A/B testing APIs). #### `AbrovaPush` — Initialization & Lifecycle #### User & Topics #### `AbrovaPushDelegate` callbacks ### Requirements - iOS 13.0+ - Xcode 14+ - Swift 5.7+ ### Best Practices - Android: https://abrova.ir/docs/push/android

Installation

AbrovaPush installs from Abrova Center in two steps. 1. Add the Abrova Center repository to `settings.gradle.kts`: ```kotlin dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://center.abrova.ir/api/packages/abrova/maven") } } } ``` Groovy: `maven { url "https://center.abrova.ir/api/packages/abrova/maven" }` 2. Add the dependency to your app's `build.gradle.kts`, then sync the project with Gradle files: ```kotlin dependencies { implementation("ir.abrova:abrova-push-android:0.1.15") } ``` #### Required Permissions The SDK requires these permissions, automatically merged via the library manifest: ```xml ```

Quick Start

Initialize AbrovaPush in your `Application` class: ```kotlin import android.app.Application import ir.abrova.push.sdk.AbrovaPush import ir.abrova.push.sdk.AbrovaPushConfig class MyApplication : Application() { override fun onCreate() { super.onCreate() val config = AbrovaPushConfig( apiKey = "ab_live_your_api_key" ) AbrovaPush.init(this, config) } } ``` Don't forget to register the Application class in your `AndroidManifest.xml`: ```xml ```

Register and Receive Messages

```kotlin import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import ir.abrova.push.sdk.AbrovaPush import ir.abrova.push.sdk.AbrovaPushCallbackAdapter import ir.abrova.push.sdk.AbrovaPushMessage class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Request notification permission on Android 13+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { requestPermissions(arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 1) } // Listen for messages and connection events AbrovaPush.setCallback(object : AbrovaPushCallbackAdapter() { override fun onMessageReceived(message: AbrovaPushMessage) { // New push notification arrived } override fun onConnectionStateChanged(connected: Boolean) { // Real-time connection state changed } override fun onRegistered(deviceId: String) { // Device registered with the Abrova backend } override fun onError(error: String) { // Connection or registration error } }) // Register on every launch: it keeps the device's token, app version // and last-seen time current on the server AbrovaPush.register( userId = "user_123", metadata = mapOf( "tier" to "premium", "language" to "en" ) ) } } ```

Handle Notification Taps

When the user taps a push notification and the app launches from the cold-start, retrieve the initial message: ```kotlin val initialMessage = AbrovaPush.getInitialMessage() if (initialMessage != null) { // Navigate based on initialMessage.deepLink or message data AbrovaPush.clearInitialMessage() } ```

User Identification

```kotlin // Set user ID after login AbrovaPush.setUserId("user_123") // Clear user ID after logout — also detaches the user on the server, // so this device stops receiving the previous user's notifications AbrovaPush.clearUserId() ```

Topics

Subscribe users to topics so you can broadcast notifications to interest groups: ```kotlin AbrovaPush.subscribeTopic("news") AbrovaPush.subscribeTopic("sports") // Later, when the user opts out AbrovaPush.unsubscribeTopic("news") ```

Rich Notifications

Rich notifications support images, action buttons, deep links, and custom sounds. These fields are populated from the dashboard or API when sending a push. ', 'No', 'Up to 3 action buttons'], ['sound', 'String?', 'No', 'Custom sound file name or "default"'], ['deepLink', 'String?', 'No', 'Deep link URI launched when the notification is tapped'], ['priority', 'String?', 'No', '"default", "high" or "low"'], ['ttl', 'Int?', 'No', 'Time-to-live in seconds before the notification expires'], ]} />

In-App Messages

In-app messages display modal, banner, fullscreen, or card layouts inside your app — useful for promotions, onboarding, and announcements. The built-in UI draws on the Activity you pass to `AbrovaPush.setCurrentActivity(this)` (call it in `onResume()`), and runs only while no `InAppMessageCallback` is set. Setting a callback means you render messages yourself: the SDK records the impression, calls `onMessageReady` and shows nothing. `onButtonClicked` / `onMessageDismissed` come only from the built-in UI, so they do not fire while a callback is set. ```kotlin import ir.abrova.push.sdk.inapp.InAppMessage import ir.abrova.push.sdk.inapp.InAppButton import ir.abrova.push.sdk.inapp.InAppMessageCallback // Built-in UI: in your Activity's onResume() AbrovaPush.setCurrentActivity(this) // Optional, only to render messages yourself (disables the built-in UI) AbrovaPush.setInAppMessageCallback(object : InAppMessageCallback { override fun onMessageReady(message: InAppMessage) { // Render the message with your own UI } override fun onButtonClicked(message: InAppMessage, button: InAppButton) { // Built-in UI only } override fun onMessageDismissed(message: InAppMessage) { // Built-in UI only } override fun onError(error: String) { // Failed to fetch or display a message } }) // Trigger on app open AbrovaPush.triggerInAppOnAppOpen() // Trigger on custom event AbrovaPush.triggerInAppEvent("viewed_product") ```

Message Inbox

Inbox messages are stored server-side (sent from Abrova Console or the Inbox API, separately from pushes) so users can revisit them, similar to a messaging app inbox. ```kotlin import ir.abrova.push.sdk.inbox.InboxFilter import ir.abrova.push.sdk.inbox.InboxCallback import ir.abrova.push.sdk.inbox.InboxMessage import ir.abrova.push.sdk.inbox.InboxMessageStatus // Fetch inbox messages AbrovaPush.getInboxMessages( filter = InboxFilter(limit = 50), onSuccess = { response -> val messages = response.messages val unread = response.unreadCount }, onError = { error -> /* handle */ } ) // Listen for real-time inbox updates AbrovaPush.getInboxManager().setCallback(object : InboxCallback { override fun onMessageReceived(message: InboxMessage) { // New inbox message arrived } override fun onMessageStatusChanged(messageId: String, status: InboxMessageStatus) { // Read / unread / archived state changed } }) // Unread count for badges val count = AbrovaPush.getInboxManager().getUnreadCount() ```

Configuration Options

```kotlin val config = AbrovaPushConfig( apiKey = "ab_live_your_api_key", notificationIcon = "ic_notification", showServiceNotification = true, showNotificationInForeground = true, autoRefresh = true ) ``` The SDK never prompts for notification permission during a refresh. The installed SDK version is available as `AbrovaPush.SDK_VERSION`. #### Delivery Tracking Every notification the device receives is confirmed to Abrova Push, so the **Delivery** page in the Abrova Console shows it as *delivered*, not just *sent*. No setup is needed. See [Delivery Receipts](/docs/push/receipts).

VoIP Calls (Optional)

For incoming-call UI on Android, add the companion VoIP SDK. The Push SDK works on its own — VoIP is only needed if your app has real calling features. ```kotlin dependencies { implementation("ir.abrova:abrova-push-android:0.1.15") implementation("ir.abrova:abrova-push-voip:0.1.0") // Optional } ``` ```kotlin import ir.abrova.push.voip.AbrovaPushVoip import ir.abrova.push.voip.VoipConfig import ir.abrova.push.voip.VoipCallback import ir.abrova.push.voip.CallData AbrovaPushVoip.initialize(this, VoipConfig(appName = "MyApp")) AbrovaPushVoip.setCallback(object : VoipCallback { override fun onCallAccepted(callData: CallData) { // Connect to your calling service (Jitsi, WebRTC, etc.) } override fun onCallDeclined(callData: CallData) { } override fun onCallTimeout(callData: CallData) { } }) ``` Send a push payload with `{"type": "voip_call", "callerName": "John"}` to trigger the incoming call screen automatically. ### API Reference Full method surface of `AbrovaPush` and supporting classes. #### `AbrovaPush` — Initialization & Lifecycle #### User & Topics #### In-App Messages #### Inbox ### Device Compatibility - **Android 5.0 Lollipop** — API 21 (minimum supported) - **Android 8.0 Oreo** — API 26 (notification channels) - **Android 13 Tiramisu** — API 33 (runtime POST_NOTIFICATIONS permission) - **Android 14** — API 34 ### Best Practices - پل FCM اندروید: https://abrova.ir/docs/push/android-fcm Abrova Push delivers to Android over its own persistent connection (TLS, port 443), so the default delivery path needs no Google Play services, no Google account and no Firebase project. Android, though, closes background connections to save battery: Doze, standby buckets and the battery managers on Xiaomi, Huawei, Samsung and others all do it, and a phone on low battery does it sooner. The FCM bridge adds a **second** delivery path through Firebase, which rides the connection Google Play services already keeps alive. It is optional and off until you upload a Firebase key, and it only works on devices that have Google Play services. Both paths carry every push and the SDK shows whichever arrives first, so a device never sees two notifications. Where Google services are blocked or missing — Iran, China, many Huawei phones — the Firebase copy simply never arrives and Abrova delivers as before.

Upload your Firebase key

1. In the [Firebase console](https://console.firebase.google.com), open (or create) the project your app is built with. 2. **Project settings → Service accounts → Generate new private key**. A JSON file downloads. 3. In [Abrova Console](https://console.abrova.ir): **Push → Settings → Android: Firebase Cloud Messaging**, then upload or paste that file. Abrova checks the key with Google before saving it and tells you if the Cloud Messaging API is disabled or the account lacks permission. The key is stored encrypted and never shown again. You can turn the bridge off without deleting it. An Admin SDK key can also read your database and manage users. Create a service account with just the **Firebase Cloud Messaging API Sender** role and upload that one.

Add the add-on to your app

The core SDK stays free of Firebase; the bridge ships as a separate package from Abrova Center. Your app needs `google-services.json` and the Google services Gradle plugin, which Firebase's own setup adds. #### Android 1. Make sure the Abrova Center repository is listed (see [Android SDK](/docs/push/android)): ```kotlin maven { url = uri("https://center.abrova.ir/api/packages/abrova/maven") } ``` 2. Add the add-on next to the Push SDK: ```gradle dependencies { implementation("ir.abrova:abrova-push-fcm:0.1.0") } ``` #### Flutter Same line in `android/app/build.gradle`. No Dart changes. #### React Native Same line in `android/app/build.gradle`. No JavaScript changes. No other code is needed: the add-on registers itself, sends the device's Firebase token with the normal device registration, and hands incoming messages to the SDK you already use. Notifications, taps, delivery confirmation and in-app suppression behave exactly as before.

Apps that already handle Firebase messages

Android delivers Firebase messages to one service only. If your app has its own `FirebaseMessagingService`, pass Abrova's messages through it: ```kotlin override fun onMessageReceived(message: RemoteMessage) { if (AbrovaPushFcm.handleMessage(message)) return // an Abrova push // your own handling } override fun onNewToken(token: String) { AbrovaPushFcm.onNewToken(token) } ``` Then remove the add-on's own service in your manifest: ```xml ```

Check it is working

Send a test push from Console, then open **Delivery**. Receipts show which path carried each notification: On a healthy connection the Abrova path usually wins; the Firebase copy matters when Android has closed that connection, which is exactly the case the bridge exists for. ### What to expect - Flutter: https://abrova.ir/docs/push/flutter

Installation

AbrovaPush installs from Abrova Center in two steps. 1. Add the package to `pubspec.yaml`, pointing at Abrova Center: ```yaml dependencies: abrova_push: hosted: https://center.abrova.ir/api/packages/abrova/pub version: ^0.1.20 ``` 2. Fetch the package: ```bash flutter pub get ``` #### Android repository The plugin uses the native Android SDK. Add the Abrova Center repository in `android/build.gradle` (or `android/settings.gradle`, wherever your project lists repositories): ```groovy allprojects { repositories { google() mavenCentral() maven { url "https://center.abrova.ir/api/packages/abrova/maven" } } } ``` #### iOS native SDK The plugin uses the native iOS SDK. Add it inside the app target in `ios/Podfile`, then run `pod install` in `ios/`: ```ruby pod 'AbrovaPushSDK', :git => 'https://github.com/Abrova-co/abrova-push-ios-sdk.git', :tag => '0.1.16' ``` #### Android Manifest Add to `android/app/src/main/AndroidManifest.xml`: ```xml ``` #### iOS Capabilities In Xcode → **Signing & Capabilities** enable: - **Push Notifications** - **Background Modes** → **Remote notifications**

Quick Start

Initialize AbrovaPush before `runApp`: ```dart import 'package:flutter/material.dart'; import 'package:abrova_push/abrova_push.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await AbrovaPush.init(AbrovaPushConfig( apiKey: 'ab_live_your_api_key', )); runApp(const MyApp()); } ```

Register and Receive Messages

```dart @override void initState() { super.initState(); // Listen for messages AbrovaPush.onMessage((message) { // message.title, message.body, message.data }); // Connection status AbrovaPush.onConnectionState((connected) { // true / false }); // Register the device AbrovaPush.register(userId: 'user_123'); } ```

Notification Tap Handling

When the app cold-starts from a notification tap, fetch the initial message before showing UI: ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); await AbrovaPush.init(config); final initialMessage = await AbrovaPush.getInitialMessage(); if (initialMessage != null) { runApp(MyApp(initialRoute: routeFor(initialMessage))); } else { runApp(const MyApp()); } } ``` For taps that happen while the app is running, listen via callback: ```dart AbrovaPush.onNotificationTapped((AbrovaPushMessage message) { // Navigate based on message.data }); ```

User Identification

```dart // Set user ID after login await AbrovaPush.setUserId('user_123'); // Clear user ID after logout await AbrovaPush.clearUserId(); // Register with user ID + metadata in one call await AbrovaPush.register( userId: 'user_123', metadata: {'appVersion': '1.0.0', 'language': 'en'}, ); ```

Topics

```dart await AbrovaPush.subscribeTopic('news'); await AbrovaPush.subscribeTopic('promotions'); await AbrovaPush.unsubscribeTopic('promotions'); ```

In-App Messages

Trigger in-app messages on app open or custom events. To display them, wrap your app in `AbrovaPushInAppOverlay`; the native SDKs do not render in-app UI for Flutter (see [In-App Messages](/docs/push/in-app)): ```dart // On app open await AbrovaPush.triggerInAppOnAppOpen(); // On custom event with properties await AbrovaPush.triggerInAppEvent('purchase_complete', properties: { 'amount': 29.99, }); // Button taps on overlay-rendered messages arrive in the overlay's onButtonTap MaterialApp( builder: (context, child) => AbrovaPushInAppOverlay( onButtonTap: (context, message, button) { // User tapped an action button }, child: child ?? const SizedBox.shrink(), ), ); ``` `AbrovaPushInAppOverlay` listens through `AbrovaPush.onInAppMessageReady`, which holds a single callback; setting your own `onInAppMessageReady` replaces the overlay's listener.

Inbox

Inbox messages are stored server-side (sent from Abrova Console or the Inbox API, separately from pushes) so users can revisit them: ```dart // Fetch inbox messages final response = await AbrovaPush.getInboxMessages( status: InboxMessageStatus.unread, limit: 50, ); // Mark as read await AbrovaPush.markInboxMessageAsRead(messageId); // Archive await AbrovaPush.archiveInboxMessage(messageId); // Unread count for badges final count = await AbrovaPush.getInboxUnreadCount(); // Listen for new inbox messages AbrovaPush.onInboxMessage((message) { // message.content.title, message.content.body }); ```

A/B Testing

```dart // Get active tests final tests = await AbrovaPush.getActiveABTests(); // Get the assigned variant for a test final variant = await AbrovaPush.getABTestVariant('test_id'); if (variant != null) { // variant.variantName, variant.content } // Track events await AbrovaPush.trackABTestImpression('test_id', 'variant_id'); await AbrovaPush.trackABTestClicked('test_id', 'variant_id'); // Listen for new variant assignments AbrovaPush.onABTestVariantAssigned((variant) { // Newly assigned to a variant }); ```

Analytics

Forward SDK events to your analytics service: ```dart AbrovaPush.setAnalyticsHandler((event, properties) { // Example: forward to Firebase Analytics FirebaseAnalytics.instance.logEvent( name: 'abrova_push_${event.name}', parameters: properties?.map((k, v) => MapEntry(k, v.toString())), ); }); ```

Log Levels

```dart import 'package:flutter/foundation.dart'; if (kReleaseMode) { await AbrovaPush.setLogLevel(AbrovaPushLogLevel.error); } else { await AbrovaPush.setLogLevel(AbrovaPushLogLevel.debug); } ``` Available levels: `none`, `error`, `warning`, `info`, `debug`, `verbose`.

Configuration Options

```dart await AbrovaPush.init(AbrovaPushConfig( apiKey: 'ab_live_your_api_key', notificationIcon: 'ic_notification', usePushKit: false, showServiceNotification: true, showNotificationInForeground: true, )); ``` Connection configuration is fetched from the server using your API key. No manual setup is needed.

VoIP Calls (Optional)

For apps with calling features, add the companion VoIP plugin for native incoming-call UI: ```yaml dependencies: abrova_push: hosted: https://center.abrova.ir/api/packages/abrova/pub version: ^0.1.20 abrova_push_voip: # Optional - for calling apps hosted: https://center.abrova.ir/api/packages/abrova/pub version: ^0.1.1 ``` ```dart await AbrovaPushVoIP.init( config: AbrovaPushVoIPConfig(appName: 'MyApp'), onCallAccepted: (callData) => navigateToCallScreen(callData), onCallDeclined: (callData) => notifyCallDeclined(callData.callId), ); // Register VoIP after AbrovaPush has issued a deviceId final deviceId = await AbrovaPush.getDeviceId(); await AbrovaPushVoIP.setApiKey(apiKey: 'ab_live_your_api_key', deviceId: deviceId); // deviceId is optional ``` Trigger an incoming call from the backend with `type: "voip_call"`: ```json { "title": "Incoming Call", "body": "John Doe is calling", "data": { "type": "voip_call", "callerName": "John Doe", "callerId": "user_456", "callType": "video" } } ```

iOS Delivery Confirmation (Optional)

**Optional, iOS only.** Android confirms delivery on its own — nothing to do there. On iOS, APNs only reports that Apple *accepted* a notification, so without this step iOS devices report `sent` and never `delivered`. Confirming delivery on iOS requires a Notification Service Extension, which is a native Xcode target. Follow the full walkthrough in the [iOS SDK guide](/docs/push/ios) — adding the target, enabling an App Group on both targets, and adding the SDK to the extension target. From Flutter, the only change is passing the same App Group to the config: ```dart await AbrovaPush.init(AbrovaPushConfig( apiKey: 'ab_live_your_api_key', // iOS only. Must match the App Group enabled on BOTH your Runner target and // the Notification Service Extension target. appGroup: 'group.com.yourcompany.yourapp', )); ``` `appGroup` is ignored on Android, so it is safe to pass unconditionally. It appears in four places: both targets' capabilities, your extension's Swift code, and `AbrovaPushConfig`. A mismatch fails silently — the extension runs, finds no device identity, and reports nothing. ### API Reference #### `AbrovaPush` — Initialization & Lifecycle #### Callbacks #### User & Topics #### Inbox #### In-App Messages #### A/B Testing ### Best Practices - React Native: https://abrova.ir/docs/push/react-native

Installation

The package installs from Abrova Center in two steps. 1. Abrova Center serves the package at this registry address: ``` https://center.abrova.ir/api/packages/abrova/npm/ ``` 2. Install the package from it (it has no dependencies of its own, so nothing else is fetched from there): ```bash npm install abrova-push-react-native --registry=https://center.abrova.ir/api/packages/abrova/npm/ # or yarn add abrova-push-react-native --registry https://center.abrova.ir/api/packages/abrova/npm/ ``` #### Android Setup The package uses the native Android SDK. Add the Abrova Center repository in `android/build.gradle` (or `android/settings.gradle`, wherever your project lists repositories): ```groovy allprojects { repositories { google() mavenCentral() maven { url "https://center.abrova.ir/api/packages/abrova/maven" } } } ``` No manifest changes are needed: the native Android SDK (`ir.abrova:abrova-push-android`) declares the permissions it needs (`INTERNET`, `ACCESS_NETWORK_STATE`, `WAKE_LOCK`, `RECEIVE_BOOT_COMPLETED`, `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_SPECIAL_USE`, `POST_NOTIFICATIONS`) and they are merged into your app's manifest automatically. #### iOS Setup The package uses the native iOS SDK. Add it inside the app target in `ios/Podfile`: ```ruby pod 'AbrovaPushSDK', :git => 'https://github.com/Abrova-co/abrova-push-ios-sdk.git', :tag => '0.1.16' ``` Then install the pods: ```bash cd ios && pod install ``` In Xcode → **Signing & Capabilities** enable: - **Push Notifications** - **Background Modes** → **Remote notifications**

Quick Start

```typescript import AbrovaPush from 'abrova-push-react-native'; // Initialize on app start await AbrovaPush.init({ apiKey: 'ab_live_your_api_key', }); // Listen for messages const unsubscribe = AbrovaPush.onMessage((message) => { // message.title, message.body, message.data }); // Register the device await AbrovaPush.register({ userId: 'user_123' }); // Later, when the component unmounts unsubscribe(); ```

React Hook

Idiomatic React Native pattern — encapsulate registration + listeners in a custom hook: ```typescript import { useEffect, useState } from 'react'; import AbrovaPush from 'abrova-push-react-native'; export function useAbrovaPush(userId: string) { const [connected, setConnected] = useState(false); const [deviceId, setDeviceId] = useState(null); useEffect(() => { const unsubMessage = AbrovaPush.onMessage((msg) => { // handle msg }); const unsubConnection = AbrovaPush.onConnectionState(setConnected); const unsubRegistered = AbrovaPush.onRegistered(setDeviceId); AbrovaPush.register({ userId }); return () => { unsubMessage(); unsubConnection(); unsubRegistered(); }; }, [userId]); return { connected, deviceId }; } ```

Notification Tap Handling

```typescript // Get the message that launched the app (from notification tap) const message = await AbrovaPush.getInitialMessage(); if (message) { // Navigate based on message.data } // While the app is running, listen for taps AbrovaPush.onNotificationTapped((message) => { // Navigate based on message.data }); // Action button taps AbrovaPush.onNotificationAction((event) => { // event.actionId, event.title, event.body, event.data, event.messageId }); ```

User Identification

```typescript // Set user ID after login await AbrovaPush.setUserId('user_123'); // Clear user ID on logout await AbrovaPush.clearUserId(); // Register with user ID and metadata await AbrovaPush.register({ userId: 'user_123', metadata: { appVersion: '1.0.0', language: 'en' }, }); ```

Topics

```typescript await AbrovaPush.subscribeTopic('news'); await AbrovaPush.subscribeTopic('promotions'); await AbrovaPush.unsubscribeTopic('promotions'); ```

In-App Messages

This package has no built-in in-app UI yet. When a trigger matches, the native SDK records the impression and emits `onInAppMessageReady`, and your app renders the message. `onInAppButtonClick` and `onInAppMessageDismissed` come only from the native SDKs' built-in UI, which the wrapper turns off, so they do not fire. ```typescript // Trigger on app open await AbrovaPush.triggerInAppOnAppOpen(); // Trigger custom event with properties await AbrovaPush.triggerInAppEvent('purchase_complete', { amount: 29.99 }); // Render the message yourself when a trigger matches AbrovaPush.onInAppMessageReady((message) => { // message.type, message.content.title, message.content.buttons, ... }); // Manual fetch / emit / dismiss const messages = await AbrovaPush.fetchInAppMessages(); await AbrovaPush.showInAppMessage(messageId); await AbrovaPush.dismissInAppMessage(); ```

Inbox

```typescript import { InboxMessageStatus } from 'abrova-push-react-native'; // Fetch inbox messages const response = await AbrovaPush.getInboxMessages({ status: InboxMessageStatus.UNREAD, limit: 50, }); // Mark as read await AbrovaPush.markInboxMessageAsRead(messageId); // Archive await AbrovaPush.archiveInboxMessage(messageId); // Unread count for badges const count = await AbrovaPush.getInboxUnreadCount(); // Mark every message as read at once await AbrovaPush.markAllInboxMessagesAsRead(); ```

A/B Testing

```typescript // Get active tests const tests = await AbrovaPush.getActiveABTests(); // Get the assigned variant for a test const variant = await AbrovaPush.getABTestVariant('test_id'); if (variant) { // variant.variantName, variant.isControlGroup } // Track events await AbrovaPush.trackABTestImpression('test_id', 'variant_id'); await AbrovaPush.trackABTestClicked('test_id', 'variant_id'); // Listen for new variant assignments AbrovaPush.onABTestVariantAssigned((variant) => { // Newly assigned to a variant }); ```

Log Levels

```typescript import { LogLevel } from 'abrova-push-react-native'; await AbrovaPush.setLogLevel(LogLevel.DEBUG); // Development await AbrovaPush.setLogLevel(LogLevel.ERROR); // Production ``` Available levels: `NONE`, `ERROR`, `WARNING`, `INFO`, `DEBUG`, `VERBOSE`.

Configuration Options

```typescript await AbrovaPush.init({ apiKey: 'ab_live_your_api_key', notificationIcon: 'ic_notification', usePushKit: false, showServiceNotification: true, appGroup: 'group.com.yourcompany.yourapp', autoRefresh: true, }); ``` Connection configuration is fetched from the server using your API key. No manual setup is needed.

VoIP Calls (Optional)

The Push SDK works on its own. For native incoming-call UI, add the separate `abrova-push-react-native-voip` package (see [VoIP Add-on](/docs/push/voip)) and send a push whose `data` contains `type: "voip_call"`: ```json { "type": "voip_call", "callerName": "John Doe", "callerId": "user_456", "callerAvatar": "https://example.com/avatar.jpg", "callType": "video" } ``` The `type: "voip_call"` is the system trigger (fixed). Caller-data keys are configurable via the VoIP SDK config. ### API Reference #### `AbrovaPush` — Initialization & Lifecycle #### Callbacks All event handlers return an unsubscribe function. #### User & Topics #### Inbox #### A/B Testing ### Best Practices - Web: https://abrova.ir/docs/push/web

Installation

The SDK installs from Abrova Center in two steps. 1. Point the `@abrova` scope at Abrova Center in your project's `.npmrc`: ``` # .npmrc (in your project root) @abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` 2. Install the package: ```bash npm install @abrova/push-web@^0.1.12 ``` #### Without a bundler The package also ships a ready-made browser build. Copy it into your public directory and load it from your own site: ```bash cp node_modules/@abrova/push-web/dist/index.umd.js public/abrova-push.umd.js ``` ```html ```

Service Worker Setup

The Web SDK uses a service worker for background notifications (Web Push). Copy it once to your public directory: ```bash cp node_modules/@abrova/push-web/service-worker.js public/abrova-push-sw.js ``` Web Push requires HTTPS. `localhost` works for development. Make sure the service worker file is served from the **root** of your public directory. Copy `service-worker.js` again whenever you upgrade `@abrova/push-web`. An older worker keeps working, but misses fixes such as SDK version reporting on re-subscription.

Quick Start

```typescript import AbrovaPush from '@abrova/push-web'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', }); abrovaPush.onMessage((message) => { // message.title, message.body, message.data }); abrovaPush.onConnectionState((state) => { // 'connecting' | 'connected' | 'disconnected' | 'error' }); abrovaPush.onRegistered((deviceId) => { // Device registered with the Abrova backend }); // Register the device — this also requests notification permission. // Once permission is granted, call register() on every page load to keep the // device's subscription, app version and last-seen time current. const deviceId = await abrovaPush.register({ userId: 'user_123' }); ```

Callbacks

All event handlers return an unsubscribe function. Call it on teardown. ```typescript // Foreground messages const unsub = abrovaPush.onMessage((message) => { // message.title, message.body }); // Connection state changes abrovaPush.onConnectionState((state) => { // 'connecting' | 'connected' | 'disconnected' | 'error' }); // Notification clicked while the browser was in background abrovaPush.onNotificationClick((message, action) => { if (message.deepLink) { window.location.href = message.deepLink; } }); // Action button clicked on a notification abrovaPush.onActionClicked((actionId, message) => { // actionId from the original push payload }); // Errors abrovaPush.onError((error) => {}); abrovaPush.onDetailedError((error) => { // error.code, error.message }); // Reconnection attempts abrovaPush.onReconnecting((state) => { // state.retryAttempt, state.nextRetryMs }); // Network state changes abrovaPush.onNetworkState((state) => { // state.isAvailable, state.networkType }); // Tab visibility changes abrovaPush.onAppState((state) => { // state.isVisible }); // Clean up unsub(); ```

User Identification

```typescript // Set user ID after login await abrovaPush.setUserId('user_123'); // Clear user ID on logout — also detaches the user on the server, // so this browser stops receiving the previous user's notifications await abrovaPush.clearUserId(); // Register with user ID in one call await abrovaPush.register({ userId: 'user_123' }); ```

Topics

```typescript await abrovaPush.subscribeTopic('news'); await abrovaPush.subscribeTopic('promotions'); await abrovaPush.unsubscribeTopic('promotions'); ```

Badge Management

Unified badge API — uses the [native Badge API](https://developer.mozilla.org/en-US/docs/Web/API/Badging_API) where available and falls back to drawing on the favicon. ```typescript abrovaPush.setBadgeCount(5); abrovaPush.clearBadge(); const count = abrovaPush.getBadgeCount(); ```

Message Inbox

Server-stored messages users can revisit, with live updates and an offline cache (0.1.6+). See the [Inbox guide](/docs/push/inbox). ```typescript abrovaPush.inbox.onMessage((message) => render(message)); abrovaPush.inbox.onUnreadCountChange((count) => setBadge(count)); const { messages, unreadCount } = await abrovaPush.inbox.getMessages({ status: 'unread' }); await abrovaPush.inbox.markAsRead(messages[0].id); await abrovaPush.inbox.markAllAsRead(); ```

Analytics

Forward SDK events to your analytics service: ```typescript abrovaPush.setAnalyticsHandler((event, properties) => { analytics.track(`abrova_push_${event}`, properties); }); // Disable if needed (e.g. for GDPR) abrovaPush.disableAnalytics(); ```

Log Levels

```typescript import { AbrovaPushLogLevel } from '@abrova/push-web'; abrovaPush.setLogLevel(AbrovaPushLogLevel.DEBUG); // Development abrovaPush.setLogLevel(AbrovaPushLogLevel.ERROR); // Production ``` Available levels: `NONE`, `ERROR`, `WARNING`, `INFO`, `DEBUG`, `VERBOSE`.

Utilities

```typescript const connected = abrovaPush.isConnected(); const deviceId = abrovaPush.getDeviceId(); const network = abrovaPush.getNetworkState(); const appState = abrovaPush.getAppState(); const initialMessage = abrovaPush.getInitialMessage(); // Static checks AbrovaPush.isSupported(); AbrovaPush.getPermissionStatus(); // Unregister the device await abrovaPush.unregister(); ```

Configuration Options

```typescript const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serviceWorkerPath: '/abrova-push-sw.js', autoRegisterServiceWorker: true, maxReconnectAttempts: 0, logLevel: AbrovaPushLogLevel.ERROR, appVersion: '2.0.0', autoRefresh: true, }); ``` The installed SDK version is exported as `SDK_VERSION`: ```typescript import { SDK_VERSION } from '@abrova/push-web'; ``` #### Delivery Tracking Notifications are confirmed as `delivered` automatically: Web Push arrivals by the service worker, and messages received over the real-time connection while the page is open by the SDK. No code needed. See [Delivery Receipts](/docs/push/receipts).

Usage Without a Bundler

For sites that don't use a bundler, load the browser build you copied in step 1: ```html My App ``` ### API Reference #### `AbrovaPush` — Initialization & Lifecycle #### `abrovaPush.inbox` — Message Inbox (0.1.6+) #### Static methods #### Callbacks All callbacks return an unsubscribe function. #### User & Topics #### Badge + Analytics + Logs ### Browser Support - **Chrome 50+** (Desktop & Android) - **Firefox 44+** - **Edge 17+** - **Safari 16+** (macOS only — iOS does not support Web Push) - **Opera 37+** ### Requirements - HTTPS required (`localhost` works for development) - Service worker file must be in the public root directory - User must grant notification permission via the browser's permission prompt ### Best Practices - Node.js: https://abrova.ir/docs/push/nodejs

Installation

The SDK installs from Abrova Center in two steps. 1. Point the `@abrova` scope at Abrova Center in your project's `.npmrc`: ``` # .npmrc (in your project root) @abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` 2. Install the package: ```bash npm install @abrova/push-node ```

Quick Start

```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: process.env.ABROVA_API_KEY, // ab_live_... serverSecret: process.env.ABROVA_SERVER_SECRET, // ab_srv_..., server only }); await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'Order Shipped', body: 'Your order #1234 has been shipped!', data: { orderId: '1234' }, }); ``` Both `apiKey` and `serverSecret` are required for all server-side operations. Find them in your [Abrova Console](https://console.abrova.ir) under project settings. **Never** ship the `serverSecret` to client-side code.

Send Push Notifications

```typescript // Send to a specific device await abrovaPush.push.sendToDevice({ deviceId: 'device-uuid', title: 'Hello', body: 'Welcome to our app!', }); // Send to all of a user's devices await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'New Message', body: 'You have a new message', }); // Send to multiple specific devices await abrovaPush.push.sendToDevices({ deviceIds: ['device-1', 'device-2', 'device-3'], title: 'Update Available', body: 'A new version is available', }); // Send to a topic await abrovaPush.push.sendToTopic({ topic: 'promotions', title: 'Flash Sale!', body: '50% off everything today only', imageUrl: 'https://example.com/sale.jpg', }); // Send to a segment await abrovaPush.push.sendToSegment({ segmentId: 'segment-uuid', title: 'Exclusive Offer', body: 'Just for our VIP customers', }); // Broadcast to every registered device await abrovaPush.push.broadcast({ title: 'App Update', body: 'Check out the new features!', }); ```

Multi-App Targeting

If your project hosts multiple apps (e.g. a shopping app and a chat app), use `appIdentifier` (the bundle ID / package name) to target a specific app: ```typescript await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'New Order', body: 'Your order is ready', appIdentifier: 'com.myshop.app', // only this app receives it }); ``` `appIdentifier` is optional. When omitted, the notification is sent to **all** apps registered under the project. Single-app projects don't need to set it.

Rich Notifications

```typescript await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'New Product', body: 'Check out our latest arrival', imageUrl: 'https://example.com/product.jpg', actions: [ { id: 'buy', title: 'Buy Now', action: 'OPEN_PRODUCT' }, { id: 'later', title: 'Save for Later' }, ], deepLink: 'myapp://product/123', data: { productId: '123' }, }); ```

Templates

Reusable copy with variable substitution. Useful when the same message shape ships in many flows (order shipped, password reset, etc.). ```typescript // Send using a template await abrovaPush.push.sendToUser({ userId: 'user-123', templateId: 'order-shipped-template', templateVariables: { orderNumber: '#1234', trackingUrl: 'https://track.example.com/1234', }, }); // Create a template const template = await abrovaPush.templates.create({ name: 'Order Shipped', title: 'Your order {{orderNumber}} has shipped!', body: 'Track your package: {{trackingUrl}}', variables: ['orderNumber', 'trackingUrl'], }); // List templates const templates = await abrovaPush.templates.list(); // Preview render const rendered = await abrovaPush.templates.render(template.id, { orderNumber: '#1234', trackingUrl: 'https://track.example.com/1234', }); ```

Inbox Messages

Push delivers a message to the device. Inbox stores it persistently so the user can revisit later. ```typescript // Send inbox message to a user await abrovaPush.inbox.sendToUser({ userId: 'user-123', content: { title: 'Welcome!', body: 'Thanks for joining us.', imageUrl: 'https://example.com/welcome.jpg', deepLink: 'myapp://onboarding', }, }); // Send to multiple users await abrovaPush.inbox.sendToUsers({ userIds: ['user-1', 'user-2', 'user-3'], content: { title: 'New Feature', body: 'Check out our latest feature' }, }); // Broadcast with expiry await abrovaPush.inbox.broadcast({ content: { title: 'Holiday Sale', body: 'Up to 70% off!' }, expiresAt: '2025-12-31T23:59:59Z', }); ```

Device Management

```typescript // List all devices const devices = await abrovaPush.devices.list(); // Subscribe a device to a topic await abrovaPush.devices.subscribeTopic('device-uuid', 'news'); await abrovaPush.devices.unsubscribeTopic('device-uuid', 'news'); // Associate device with a user await abrovaPush.devices.setUserId('device-uuid', 'user-123'); // Delete a device await abrovaPush.devices.delete('device-uuid'); // Paginated, filtered list (includes inactive devices unless isActive is set) const { items, total } = await abrovaPush.devices.listPage({ platform: 'ios', isActive: true, page: 1, limit: 50, }); // Values you can filter on (app versions, SDK versions, failure reasons) const options = await abrovaPush.devices.filterOptions(); // Stop sending to a device but keep its history, and undo it await abrovaPush.devices.unregister('device-uuid'); await abrovaPush.devices.reactivate('device-uuid'); ```

Segments

Segments evaluate user metadata server-side. Use them when you need "all users with more than 10 orders" or "all users in Tehran on the premium tier." ```typescript // Create a segment const segment = await abrovaPush.segments.create({ name: 'VIP Customers', description: 'Customers with more than 10 orders', filters: [ { field: 'metadata.orders', operator: 'greater_than', value: 10 }, ], }); // Get devices in a segment const devices = await abrovaPush.segments.getDevices(segment.id); // Recalculate segment membership (e.g. after metadata changes) await abrovaPush.segments.recalculate(segment.id); ```

Scheduled Messages

```typescript // Schedule a message const scheduled = await abrovaPush.scheduled.create({ title: 'Reminder', body: 'Don\'t forget to complete your purchase!', targetType: 'user', targetValue: 'user-123', scheduledAt: '2025-02-14T09:00:00Z', timezone: 'Asia/Tehran', }); // List pending messages const pending = await abrovaPush.scheduled.listPending(); // Cancel a scheduled message await abrovaPush.scheduled.cancel(scheduled.id); ```

Webhooks

Subscribe to message (`message.sent`, `message.delivered`, `message.failed`, `message.no_recipients`) and device (`device.registered`, `device.unregistered`, `device.updated`) events so your backend can react in real time. ```typescript // Create a webhook const webhook = await abrovaPush.webhooks.create({ name: 'Order Events', url: 'https://example.com/webhooks/abrova-push', events: ['message.sent', 'message.delivered', 'message.failed'], secret: 'my-secret-key', }); // Test a webhook await abrovaPush.webhooks.test(webhook.id, 'message.delivered'); // Read delivery logs for debugging const logs = await abrovaPush.webhooks.getLogs(webhook.id); ```

Analytics

```typescript // Device and message totals, delivery rate, platform distribution const overview = await abrovaPush.analytics.getOverview(); // Daily breakdown const daily = await abrovaPush.analytics.getDaily(30); // last 30 days // App-level totals (devices per platform, topics, users) const stats = await abrovaPush.analytics.getStats(); ``` #### Delivery receipts A send's `success` count means the push service *accepted* the notification. Receipts show what happened on each device. See [Delivery Receipts](/docs/push/receipts). ```typescript const { messageId } = await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'Hi', body: 'Hello' }); // Per device, for one message const receipts = await abrovaPush.receipts.getForMessage(messageId!); const stats = await abrovaPush.receipts.getMessageStats(messageId!); // Receipt log with filters const { items, total } = await abrovaPush.receipts.list({ status: 'failed', platform: 'android', startDate: new Date(Date.now() - 7 * 86400_000), page: 1, limit: 100, }); // Delivery analytics (default: last 30 days) const analytics = await abrovaPush.receipts.analytics({ bucket: 'day' }); console.log(analytics.totals, analytics.byPlatform, analytics.topFailureReasons); ```

A/B Tests and In-App Messages

Create A/B tests and in-app messages in Abrova Console.

Error Handling

```typescript import { AbrovaPush, AbrovaPushError } from '@abrova/push-node'; try { await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'Hello', body: 'World', }); } catch (error) { if (error instanceof AbrovaPushError) { // error.statusCode, error.message, error.response } } ```

Configuration

```typescript const abrovaPush = new AbrovaPush({ apiKey: process.env.ABROVA_API_KEY!, serverSecret: process.env.ABROVA_SERVER_SECRET!, }); ``` #### Environment Variables We recommend storing credentials in environment variables, never in version control: ```bash # .env ABROVA_API_KEY=ab_live_your_api_key ABROVA_SERVER_SECRET=ab_srv_your_server_secret ``` #### Credential Reference ### API Reference #### `push` — sending notifications #### `inbox` — persistent message center #### `devices` — device management #### `receipts` — per-device delivery #### `templates` — reusable copy #### `segments` — audience targeting #### `scheduled` — time-based delivery #### `webhooks` — delivery event subscriptions #### `analytics` — delivery + engagement metrics ### Best Practices - Callbacks: https://abrova.ir/docs/push/callbacks Monitor SDK state and events with these callbacks. The callback set is shared across SDKs; syntax varies by platform.

Flutter

#### Message Received ```dart AbrovaPush.onMessage((message) { print('Title: ${message.title}'); print('Body: ${message.body}'); print('Data: ${message.data}'); // Show local notification or update UI }); ``` #### Connection State ```dart AbrovaPush.onConnectionState((connected) { if (connected) { print('Connected to server'); } else { print('Disconnected from server'); } }); ``` #### Registration Success ```dart AbrovaPush.onRegistered((deviceId) { print('Device registered with ID: $deviceId'); // Store deviceId for later use }); ``` #### Error Handling ```dart // Simple error callback AbrovaPush.onError((error) { print('Error: $error'); }); // Detailed error with codes AbrovaPush.onDetailedError((error) { print('Code: ${error.code}'); print('Message: ${error.message}'); print('Details: ${error.details}'); }); ``` #### Network State ```dart AbrovaPush.onNetworkState((state) { print('Network available: ${state.isAvailable}'); print('Network type: ${state.networkType}'); // wifi, cellular, ethernet, vpn, none, unknown }); ``` #### App State ```dart AbrovaPush.onAppState((state) { print('In foreground: ${state.isInForeground}'); // SDK auto-reconnects when app returns to foreground }); ```

Android (Kotlin)

The Android SDK uses a single `AbrovaPushCallback` interface — implement the methods you need and register it once via `setCallback(...)`. Extend `AbrovaPushCallbackAdapter` for empty defaults on the methods you don't override. ```kotlin import ir.abrova.push.sdk.AbrovaPush import ir.abrova.push.sdk.AbrovaPushCallback import ir.abrova.push.sdk.AbrovaPushCallbackAdapter import ir.abrova.push.sdk.AbrovaPushMessage import ir.abrova.push.sdk.AbrovaPushError AbrovaPush.setCallback(object : AbrovaPushCallback { override fun onMessageReceived(message: AbrovaPushMessage) { Log.d("AbrovaPush", "Title: ${message.title}") Log.d("AbrovaPush", "Body: ${message.body}") Log.d("AbrovaPush", "Data: ${message.data}") } override fun onConnectionStateChanged(connected: Boolean) { Log.d("AbrovaPush", if (connected) "Connected" else "Disconnected") } override fun onRegistered(deviceId: String) { Log.d("AbrovaPush", "Device registered: $deviceId") } override fun onError(error: String) { Log.e("AbrovaPush", "Error: $error") } override fun onDetailedError(error: AbrovaPushError) { Log.e("AbrovaPush", "Code: ${error.code}, Message: ${error.message}") } override fun onReconnecting(attempt: Int, nextRetryMs: Long) { Log.d("AbrovaPush", "Retry $attempt, next in ${nextRetryMs}ms") } override fun onNetworkStateChanged(isAvailable: Boolean, networkType: String) { Log.d("AbrovaPush", "Network: $isAvailable ($networkType)") // networkType: wifi, cellular, ethernet, vpn, none, unknown } override fun onAppStateChanged(isInForeground: Boolean) { Log.d("AbrovaPush", "Foreground: $isInForeground") } override fun onAppUpdated(previousVersion: String, currentVersion: String, needsReregistration: Boolean) { Log.d("AbrovaPush", "Updated $previousVersion → $currentVersion") if (needsReregistration) { AbrovaPush.register(userId = "user_123") } } override fun onNotificationTapped(message: AbrovaPushMessage) { Log.d("AbrovaPush", "Notification tapped: ${message.title}") // Handle when app is in foreground / resumed } override fun onNotificationAction(message: AbrovaPushMessage, actionId: String) { Log.d("AbrovaPush", "Action clicked: $actionId for ${message.title}") } }) ``` #### Adapter for Selected Callbacks ```kotlin // Only override what you need AbrovaPush.setCallback(object : AbrovaPushCallbackAdapter() { override fun onMessageReceived(message: AbrovaPushMessage) { showNotification(message) } override fun onNotificationTapped(message: AbrovaPushMessage) { navigateToDeepLink(message.data?.get("deepLink") as? String) } }) ``` #### Initial Message (App Opened from Notification) ```kotlin // Returns the notification that launched the app, or null val initialMessage = AbrovaPush.getInitialMessage() if (initialMessage != null) { val deepLink = initialMessage.data?.get("deepLink") as? String if (deepLink != null) { // Navigate to deep link } } ``` #### Analytics Handler Not supported on Android: the handler is never called. Use the `AbrovaPushCallback` methods above for telemetry. ```kotlin import ir.abrova.push.sdk.AbrovaPush import ir.abrova.push.sdk.AbrovaPushAnalyticsCallback import ir.abrova.push.sdk.AbrovaPushAnalyticsEvent AbrovaPush.setAnalyticsHandler(AbrovaPushAnalyticsCallback { event, properties -> Log.d("AbrovaPush", "Event: $event, Properties: $properties") // Send to your analytics service (Firebase, Mixpanel, etc.) when (event) { AbrovaPushAnalyticsEvent.MESSAGE_RECEIVED -> FirebaseAnalytics.getInstance(context).logEvent("push_received", null) AbrovaPushAnalyticsEvent.NOTIFICATION_CLICKED -> FirebaseAnalytics.getInstance(context).logEvent("push_clicked", null) AbrovaPushAnalyticsEvent.CONNECTION_ERROR -> FirebaseAnalytics.getInstance(context).logEvent("push_error", null) else -> { /* ignore */ } } }) // Disable / re-enable / check AbrovaPush.disableAnalytics() val enabled = AbrovaPush.isAnalyticsEnabled() ```

React Native

#### Message Received ```typescript const unsubscribe = AbrovaPush.onMessage((message) => { console.log('Title:', message.title); console.log('Body:', message.body); console.log('Data:', message.data); }); // Clean up in useEffect useEffect(() => { return () => unsubscribe(); }, []); ``` #### Connection State ```typescript const unsubscribe = AbrovaPush.onConnectionState((connected) => { console.log(connected ? 'Connected' : 'Disconnected'); }); ``` #### Registration Success ```typescript const unsubscribe = AbrovaPush.onRegistered((deviceId) => { console.log('Device ID:', deviceId); }); ``` #### Complete Hook Example ```typescript import { useEffect, useState } from 'react'; import AbrovaPush from 'abrova-push-react-native'; export function useAbrovaPush(userId: string) { const [connected, setConnected] = useState(false); const [deviceId, setDeviceId] = useState(null); useEffect(() => { const unsubMessage = AbrovaPush.onMessage((msg) => { console.log('Message:', msg); }); const unsubConnection = AbrovaPush.onConnectionState(setConnected); const unsubRegistered = AbrovaPush.onRegistered(setDeviceId); AbrovaPush.register({ userId }); return () => { unsubMessage(); unsubConnection(); unsubRegistered(); }; }, [userId]); return { connected, deviceId }; } ```

iOS (Swift)

The iOS SDK uses a delegate pattern. Conform to `AbrovaPushDelegate` and assign it via `AbrovaPush.shared.delegate`. All methods are optional via the default extension — implement only the ones you need. ```swift import AbrovaPushSDK class PushHandler: NSObject, AbrovaPushDelegate { func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveMessage message: AbrovaPushMessage) { print("Title: \(message.title)") print("Body: \(message.body)") print("Data: \(message.data ?? [:])") } func abrovaPush(_ abrovaPush: AbrovaPush, didChangeConnectionState connected: Bool) { print(connected ? "Connected" : "Disconnected") } func abrovaPush(_ abrovaPush: AbrovaPush, didRegisterWithDeviceId deviceId: String) { print("Device registered: \(deviceId)") } func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveAPNsToken token: String) { print("APNs token: \(token)") } func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveVoIPToken token: String) { print("VoIP token: \(token)") } func abrovaPush(_ abrovaPush: AbrovaPush, didFailWithError error: Error) { print("Error: \(error.localizedDescription)") } func abrovaPush(_ abrovaPush: AbrovaPush, didFailWithDetailedError error: AbrovaPushError) { print("Code: \(error.code), Message: \(error.message)") } func abrovaPush(_ abrovaPush: AbrovaPush, didStartReconnecting state: ReconnectionState) { print("Retry \(state.attempt), next in \(state.nextRetryMs)ms") } func abrovaPush(_ abrovaPush: AbrovaPush, didChangeNetworkState state: NetworkState) { print("Network: \(state.isAvailable) (\(state.networkType))") } func abrovaPush(_ abrovaPush: AbrovaPush, didChangeAppState state: AppState) { print("Foreground: \(state.isInForeground)") } func abrovaPush(_ abrovaPush: AbrovaPush, didDetectAppUpdate info: AppUpdateInfo) { print("Updated \(info.previousVersion) → \(info.currentVersion)") if info.needsReregistration { AbrovaPush.shared.register(userId: "user_123") } } func abrovaPush(_ abrovaPush: AbrovaPush, didTapNotification message: AbrovaPushMessage) { print("Notification tapped: \(message.title)") } func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveNotificationAction action: NotificationAction, forMessage message: AbrovaPushMessage) { print("Action: \(action.id) for \(message.title)") } } // Wire it up once (e.g., in AppDelegate or App init). // `delegate` is weak, so keep a strong reference to the handler (e.g., a property). let pushHandler = PushHandler() AbrovaPush.shared.delegate = pushHandler ``` #### Initial Message (App Opened from Notification) ```swift // Returns the notification that launched the app, or nil if let initialMessage = AbrovaPush.shared.getInitialMessage() { if let deepLink = initialMessage.deepLink ?? (initialMessage.data?["deepLink"]?.value as? String) { // Navigate to deep link } AbrovaPush.shared.clearInitialMessage() } // If the app was opened by tapping an action button on the launching notification if let clicked = AbrovaPush.shared.getClickedAction() { print("Launched via action \(clicked.action.id) on \(clicked.message.title)") } ``` The iOS SDK does not currently expose `setAnalyticsHandler`. For SDK telemetry, observe the relevant delegate methods (`didReceiveMessage`, `didChangeConnectionState`, `didFailWithDetailedError`, etc.) and forward those events to your analytics provider directly. Flutter and Web support `setAnalyticsHandler`; Android exposes it but it is not supported there, and the React Native wrapper does not expose it either.

Web

#### Message Received ```typescript abrovaPush.onMessage((message) => { console.log('Title:', message.title); console.log('Body:', message.body); console.log('Data:', message.data); // Show notification or update UI }); ``` #### Connection State ```typescript abrovaPush.onConnectionState((state) => { console.log('Connection state:', state); // 'connecting' | 'connected' | 'disconnected' | 'error' }); ``` #### Registration Success ```typescript abrovaPush.onRegistered((deviceId) => { console.log('Device ID:', deviceId); }); ``` #### Notification Click ```typescript abrovaPush.onNotificationClick((message, action) => { console.log('Notification clicked:', message); console.log('Action:', action); // Navigate to specific page based on message data if (message.data?.url) { window.location.href = message.data.url; } }); ``` #### Action Button Clicked ```typescript abrovaPush.onActionClicked((actionId, message) => { console.log('Action clicked:', actionId); console.log('Message:', message); // Handle specific actions switch (actionId) { case 'view': window.location.href = message.data?.deepLink || '/'; break; case 'dismiss': // Handle dismiss break; } }); ``` #### Error Handling ```typescript import { AbrovaPushErrorCode } from '@abrova/push-web'; // Simple error callback abrovaPush.onError((error) => { console.error('Error:', error.message); }); // Detailed error with codes abrovaPush.onDetailedError((error) => { console.error('Code:', error.code); console.error('Message:', error.message); console.error('Details:', error.details); switch (error.code) { case AbrovaPushErrorCode.CONNECTION_FAILED: // Handle connection failure break; case AbrovaPushErrorCode.PERMISSION_DENIED: // Handle permission denied break; } }); ``` #### Reconnecting State ```typescript abrovaPush.onReconnecting((state) => { console.log('Retry attempt:', state.retryAttempt); console.log('Next retry in:', state.nextRetryMs, 'ms'); console.log('Max attempts:', state.maxRetryAttempts); // Show reconnecting UI showReconnectingBanner(state.retryAttempt, state.maxRetryAttempts); }); ``` #### Network State ```typescript abrovaPush.onNetworkState((state) => { console.log('Network available:', state.isAvailable); console.log('Network type:', state.networkType); // wifi, cellular, ethernet, none, unknown if (!state.isAvailable) { showOfflineBanner(); } }); // Get current network state const networkState = abrovaPush.getNetworkState(); ``` #### App State (Visibility) ```typescript abrovaPush.onAppState((state) => { console.log('Is visible:', state.isVisible); console.log('Visibility state:', state.visibilityState); // visible, hidden, prerender // SDK auto-reconnects when page becomes visible }); // Get current app state const appState = abrovaPush.getAppState(); ``` #### Initial Message (App Opened from Notification) ```typescript // Get the notification that launched/opened the app const initialMessage = abrovaPush.getInitialMessage(); if (initialMessage) { console.log('App opened from notification:', initialMessage.title); // Navigate to relevant page if (initialMessage.data?.deepLink) { window.location.href = initialMessage.data.deepLink; } } ``` #### Analytics Handler ```typescript import { AbrovaPushAnalyticsEvent } from '@abrova/push-web'; abrovaPush.setAnalyticsHandler((event, properties) => { console.log('Event:', event, properties); // Send to your analytics service switch (event) { case AbrovaPushAnalyticsEvent.MESSAGE_RECEIVED: analytics.track('push_received', properties); break; case AbrovaPushAnalyticsEvent.NOTIFICATION_CLICKED: analytics.track('push_clicked', properties); break; case AbrovaPushAnalyticsEvent.CONNECTION_ERROR: analytics.track('push_error', properties); break; } }); // Disable analytics abrovaPush.disableAnalytics(); ``` - آمار SDK: https://abrova.ir/docs/push/sdk-analytics Track SDK events with custom analytics handlers to integrate with your analytics service. ### Flutter #### Setting Up Analytics ```dart AbrovaPush.setAnalyticsHandler((event, properties) { print('Event: $event'); print('Properties: $properties'); // Send to your analytics service FirebaseAnalytics.instance.logEvent( name: 'abrovaPush_${event.name}', parameters: properties?.map((k, v) => MapEntry(k, v.toString())), ); }); ``` #### Handling Specific Events ```dart AbrovaPush.setAnalyticsHandler((event, properties) { switch (event) { case AbrovaPushAnalyticsEvent.messageReceived: // Track message received analytics.track('push_received', properties); break; case AbrovaPushAnalyticsEvent.connected: // Track successful connection analytics.track('push_connected'); break; case AbrovaPushAnalyticsEvent.connectionError: // Track connection failures FirebaseCrashlytics.instance.recordError( Exception('Push connection error'), null, reason: properties?['error_code']?.toString() ?? properties?['error'], ); break; default: break; } }); ``` The Flutter SDK's `AbrovaPushAnalyticsEvent` is a Dart enum with its own set of values: `sdkInitialized`, `deviceRegistered`, `deviceUnregistered`, `messageReceived`, `messageDisplayed`, `connected`, `disconnected`, `connectionError`, `retryStarted`, `topicSubscribed`, `topicUnsubscribed`, `networkStateChanged`, `appStateChanged`, `appUpdated`, the in-app events (`inAppMessageDisplayed`, `inAppButtonClicked`, `inAppMessageDismissed`, `inAppMessagesFetched`), the inbox events (`inboxMessagesFetched`, `inboxMessageRead`, `inboxMessageArchived`, `inboxMessageDeleted`) and the A/B test events (`abTestVariantAssigned`, `abTestImpression`, `abTestOpened`, `abTestClicked`). It has no notification-clicked or permission events; use `AbrovaPush.onNotificationTapped` for taps. Property keys are snake_case (for example `error_code`, `error_message`). Use `AbrovaPush.disableAnalytics()` / `AbrovaPush.enableAnalytics()` and the `AbrovaPush.isAnalyticsEnabled` getter to toggle it. ### Android (Kotlin) Not supported on Android. The native Android SDK has `setAnalyticsHandler`, `enableAnalytics`, `disableAnalytics` and `isAnalyticsEnabled`, but the handler below is never called. Forward the events you need from your `AbrovaPushCallback` (see [Callbacks](/docs/push/callbacks)). The Flutter SDK's handler is not affected: it emits its events from Dart. #### Setting Up Analytics ```kotlin import ir.abrova.push.sdk.AbrovaPush import ir.abrova.push.sdk.AbrovaPushAnalyticsCallback import ir.abrova.push.sdk.AbrovaPushAnalyticsEvent AbrovaPush.setAnalyticsHandler(AbrovaPushAnalyticsCallback { event, properties -> Log.d("AbrovaPush", "Event: $event") Log.d("AbrovaPush", "Properties: $properties") // Send to your analytics service val params = Bundle().apply { properties.forEach { (k, v) -> putString(k, v?.toString()) } } FirebaseAnalytics.getInstance(context).logEvent("abrovaPush_$event", params) }) ``` #### Handling Specific Events ```kotlin AbrovaPush.setAnalyticsHandler(AbrovaPushAnalyticsCallback { event, properties -> when (event) { AbrovaPushAnalyticsEvent.MESSAGE_RECEIVED -> { // Track message received analytics.track("push_received", properties) } AbrovaPushAnalyticsEvent.NOTIFICATION_CLICKED -> { // Track notification tap analytics.track("push_clicked", properties) } AbrovaPushAnalyticsEvent.CONNECTED -> { // Track successful connection analytics.track("push_connected") } AbrovaPushAnalyticsEvent.CONNECTION_ERROR -> { // Track connection failures FirebaseCrashlytics.getInstance().recordException( Exception("Push connection error: ${properties["errorCode"]}") ) } else -> { /* ignore */ } } }) ``` #### Disable Analytics ```kotlin // Disable analytics tracking AbrovaPush.disableAnalytics() // Check if analytics is enabled val isEnabled = AbrovaPush.isAnalyticsEnabled() ``` ### iOS (Swift) The iOS SDK does not currently expose `setAnalyticsHandler`. To collect SDK telemetry on iOS, conform to `AbrovaPushDelegate` and forward the events you care about — `didReceiveMessage`, `didChangeConnectionState`, `didFailWithDetailedError`, `didStartReconnecting`, `didTapNotification`, `didReceiveNotificationAction` — to your analytics provider directly. See the [Callbacks](/docs/push/callbacks) page for the full delegate surface. ```swift import AbrovaPush class PushAnalyticsForwarder: NSObject, AbrovaPushDelegate { func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveMessage message: AbrovaPushMessage) { Analytics.logEvent("push_received", parameters: [ "messageId": message.messageId ?? "", "title": message.title, ]) } func abrovaPush(_ abrovaPush: AbrovaPush, didTapNotification message: AbrovaPushMessage) { Analytics.logEvent("push_clicked", parameters: [ "messageId": message.messageId ?? "", ]) } func abrovaPush(_ abrovaPush: AbrovaPush, didFailWithDetailedError error: AbrovaPushError) { Analytics.logEvent("push_error", parameters: [ "errorCode": error.code, "errorMessage": error.message, ]) } func abrovaPush(_ abrovaPush: AbrovaPush, didChangeConnectionState connected: Bool) { Analytics.logEvent(connected ? "push_connected" : "push_disconnected", parameters: nil) } } // `delegate` is a weak reference: keep the forwarder alive yourself let forwarder = PushAnalyticsForwarder() AbrovaPush.shared.delegate = forwarder ``` ### React Native The React Native SDK (`abrova-push-react-native`) does not expose `setAnalyticsHandler`. Forward the events you need from its listeners (for example `onMessage`, `onNotificationTapped`, `onConnectionState`, `onDetailedError`) to your analytics provider directly. ### Web #### Setting Up Analytics ```typescript import AbrovaPush, { AbrovaPushAnalyticsEvent } from '@abrova/push-web'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', }); // Set up analytics handler abrovaPush.setAnalyticsHandler((event, properties) => { console.log('Analytics:', event, properties); // Send to your analytics service (Google Analytics, Mixpanel, etc.) gtag('event', `abrovaPush_${event}`, properties); }); ``` #### Handling Specific Events ```typescript abrovaPush.setAnalyticsHandler((event, properties) => { switch (event) { case AbrovaPushAnalyticsEvent.SDK_INITIALIZED: // SDK was initialized analytics.track('push_sdk_init', properties); break; case AbrovaPushAnalyticsEvent.DEVICE_REGISTERED: // Device registered successfully analytics.track('push_registered', { deviceId: properties?.deviceId, userId: properties?.userId, }); break; case AbrovaPushAnalyticsEvent.MESSAGE_RECEIVED: // Push message received analytics.track('push_received', { messageId: properties?.messageId, title: properties?.title, silent: properties?.silent, }); break; case AbrovaPushAnalyticsEvent.MESSAGE_DISPLAYED: // Notification was shown analytics.track('push_displayed', properties); break; case AbrovaPushAnalyticsEvent.NOTIFICATION_CLICKED: // User tapped notification analytics.track('push_clicked', { messageId: properties?.messageId, action: properties?.action, }); break; case AbrovaPushAnalyticsEvent.ACTION_CLICKED: // User tapped action button analytics.track('push_action', { actionId: properties?.actionId, messageId: properties?.messageId, }); break; case AbrovaPushAnalyticsEvent.CONNECTED: // Connected analytics.track('push_connected'); break; case AbrovaPushAnalyticsEvent.DISCONNECTED: // Disconnected analytics.track('push_disconnected'); break; case AbrovaPushAnalyticsEvent.CONNECTION_ERROR: // Connection error occurred analytics.track('push_error', { errorCode: properties?.errorCode, errorMessage: properties?.errorMessage, }); break; case AbrovaPushAnalyticsEvent.RETRY_STARTED: // Reconnection attempt analytics.track('push_retry', { attempt: properties?.retryAttempt, }); break; case AbrovaPushAnalyticsEvent.TOPIC_SUBSCRIBED: // Topic subscribed analytics.track('push_topic_sub', { topic: properties?.topic }); break; case AbrovaPushAnalyticsEvent.PERMISSION_GRANTED: // User granted notification permission analytics.track('push_permission_granted'); break; case AbrovaPushAnalyticsEvent.PERMISSION_DENIED: // User denied permission analytics.track('push_permission_denied'); break; } }); ``` #### Disable Analytics ```typescript // Disable analytics tracking abrovaPush.disableAnalytics(); // Check if analytics is enabled const isEnabled = abrovaPush.isAnalyticsEnabled(); ``` ### Available Events These are the events of the Web SDK (`AbrovaPushAnalyticsEvent.X`, string values in camelCase). The Android enum has the same names, but analytics events are not supported on Android (see above). Property names below are those sent by the Web SDK. The Flutter event set differs (see above).
Event Description Properties
SDK_INITIALIZED SDK was initialized -
DEVICE_REGISTERED Device registered successfully deviceId, userId
DEVICE_UNREGISTERED Device was unregistered deviceId
MESSAGE_RECEIVED Push message received messageId, title, silent, hasImage
MESSAGE_DISPLAYED Notification was shown to user messageId, title
NOTIFICATION_CLICKED User tapped notification messageId, title, action
ACTION_CLICKED User tapped action button actionId, messageId
CONNECTED Connected successfully -
DISCONNECTED Disconnected -
CONNECTION_ERROR Connection error occurred errorCode, errorMessage, details
RETRY_STARTED Reconnection attempt started retryAttempt, nextRetryMs
TOPIC_SUBSCRIBED Subscribed to topic topic
TOPIC_UNSUBSCRIBED Unsubscribed from topic topic
NETWORK_STATE_CHANGED Network state changed isAvailable, networkType
APP_STATE_CHANGED App visibility changed isVisible, visibilityState
PERMISSION_REQUESTED Permission prompt shown -
PERMISSION_GRANTED User granted permission -
PERMISSION_DENIED User denied permission -
### Integration Examples #### Google Analytics 4 ```typescript abrovaPush.setAnalyticsHandler((event, properties) => { gtag('event', `abrovaPush_${event}`, { ...properties, send_to: 'GA_MEASUREMENT_ID', }); }); ``` #### Mixpanel ```typescript abrovaPush.setAnalyticsHandler((event, properties) => { mixpanel.track(`AbrovaPush: ${event}`, properties); }); ``` #### Amplitude ```typescript abrovaPush.setAnalyticsHandler((event, properties) => { amplitude.track(`abrovaPush_${event}`, properties); }); ``` - موضوع‌ها: https://abrova.ir/docs/push/topics Subscribe to topics for targeted group messaging.

What are Topics?

Topics allow you to send messages to groups of devices that have subscribed to a specific topic. Useful for: - **News categories** — sports, tech, entertainment - **User preferences** — promotions, updates, alerts - **Geographic targeting** — cities, regions, stores - **App features** — beta users, premium members

Subscribe to Topics

#### Flutter ```dart // Subscribe to a single topic await AbrovaPush.subscribeTopic('news'); await AbrovaPush.subscribeTopic('promotions'); await AbrovaPush.subscribeTopic('sports'); // Subscribe to several topics: call subscribeTopic once per topic for (final topic in ['tech', 'gaming', 'movies']) { await AbrovaPush.subscribeTopic(topic); } ``` #### React Native ```typescript // Subscribe to a topic await AbrovaPush.subscribeTopic('news'); await AbrovaPush.subscribeTopic('promotions'); ``` #### Android (Kotlin) ```kotlin // Subscribe to a topic AbrovaPush.subscribeTopic("news") AbrovaPush.subscribeTopic("promotions") AbrovaPush.subscribeTopic("sports") ``` #### iOS (Swift) ```swift // Subscribe to a topic AbrovaPush.shared.subscribeTopic("news") AbrovaPush.shared.subscribeTopic("promotions") AbrovaPush.shared.subscribeTopic("sports") ``` #### Web ```typescript // Subscribe to a topic await abrovaPush.subscribeTopic('news'); await abrovaPush.subscribeTopic('promotions'); ```

Unsubscribe from Topics

#### Flutter ```dart // Unsubscribe from a single topic await AbrovaPush.unsubscribeTopic('promotions'); // Unsubscribe from several topics: call unsubscribeTopic once per topic for (final topic in ['sports', 'gaming']) { await AbrovaPush.unsubscribeTopic(topic); } ``` #### React Native ```typescript await AbrovaPush.unsubscribeTopic('promotions'); ``` #### Android (Kotlin) ```kotlin // Unsubscribe from a single topic AbrovaPush.unsubscribeTopic("promotions") ``` #### iOS (Swift) ```swift // Unsubscribe from a single topic AbrovaPush.shared.unsubscribeTopic("promotions") ``` #### Web ```typescript await abrovaPush.unsubscribeTopic('promotions'); ```

Get Subscribed Topics

The client SDKs do not expose a method to list a device's subscribed topics. Keep track of the topics your app subscribes to (for example in local storage), or open a device's profile in Abrova Console (Abrova Push → **Devices**) to see its topics. On the server, segment previews (`abrovaPush.segments.preview()`) include each matching device's `topics`, and a segment filter on the `topics` field targets subscribers of a topic.

Topic Naming Rules

Topic names aren't validated by the server, so stick to these conventions: - Use lowercase letters, numbers, and underscores - Avoid spaces, commas and the characters `/`, `#`, `+` in topic names. - Topic names are case-sensitive: `News` and `news` are different topics - Examples: `news`, `sports_updates`, `store_123` - مدیریت خطا: https://abrova.ir/docs/push/errors Handle errors gracefully with detailed error codes. All SDKs use standardized numeric error codes for consistent error handling across platforms.

Connection Errors (1000-1099)

Subscription Errors (1100-1199)

Message Errors (1200-1299)

#### Configuration Errors (1300-1399)

Registration Errors (1400-1499)

#### State Errors (1500-1599)

Permission Errors (1600-1699)

Web SDK only. Android and Flutter define no 1600 codes; on iOS, 1600 / 1601 are the A/B testing errors `variantNotFound` / `testNotFound`.

Flutter

```dart AbrovaPush.onDetailedError((error) async { switch (error.errorCode) { case AbrovaPushErrorCode.connectionFailed: // Show "Connection failed" message // SDK will auto-retry break; case AbrovaPushErrorCode.authenticationFailed: // Invalid API key - check dashboard break; case AbrovaPushErrorCode.registrationFailed: // Retry registration await AbrovaPush.register(userId: userId); break; case AbrovaPushErrorCode.subscriptionFailed: // Topic subscription failed break; case AbrovaPushErrorCode.networkError: // Show offline indicator // SDK will reconnect when network returns break; default: print('Error: ${error.message}'); } }); ```

Web

```typescript import { AbrovaPushErrorCode } from '@abrova/push-web'; abrovaPush.onDetailedError((error) => { switch (error.code) { case AbrovaPushErrorCode.CONNECTION_FAILED: // Show "Connection failed" message — SDK auto-retries break; case AbrovaPushErrorCode.AUTHENTICATION_FAILED: // Invalid API key — check dashboard break; case AbrovaPushErrorCode.REGISTRATION_FAILED: // Retry registration abrovaPush.register({ userId }); break; case AbrovaPushErrorCode.SUBSCRIPTION_FAILED: // Topic subscription failed break; case AbrovaPushErrorCode.NETWORK_ERROR: // Show offline indicator — SDK reconnects when network returns break; case AbrovaPushErrorCode.PERMISSION_DENIED: // User denied notification permission showPermissionRequestUI(); break; default: console.error('Error:', error.code, error.message, error.details); } }); ```

Simple Error Handler

For projects that just need to log errors without branching: ```dart AbrovaPush.onError((error) { // Log to crash reporting FirebaseCrashlytics.instance.recordError( Exception(error), null, reason: 'AbrovaPush error', ); }); ```

Best Practices

- ارسال: https://abrova.ir/docs/push/send Send push notifications from your server using the REST API. This REST API must only be used from your backend server, **never** from mobile apps or client-side code. The server secret can be extracted from mobile apps by attackers, which would allow them to send unauthorized push notifications. **For mobile apps:** use the native SDKs (iOS, Android, Flutter, React Native) which only support *receiving* notifications. To send notifications from your app, make a request to your own backend server, which then calls the AbrovaPush API.

Authentication

All API requests require both your **API Key** and **Server Secret** in the headers. ```bash curl -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ https://push-api.abrova.ir/push/... ``` #### Required Headers For additional security, you can configure an IP whitelist in your project settings. When enabled, only requests from whitelisted IP addresses can use the server secret.

Send Notification

The REST API exposes six send endpoints, one per audience shape: a single user (all their devices), a single device, multiple devices by ID, a topic, a segment, or every registered device. #### Send to User Send to all devices of a user. ```bash curl -X POST https://push-api.abrova.ir/push/send-to-user \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_123", "title": "Hello!", "body": "You have a new message", "data": { "messageId": "msg_456", "action": "open_chat" } }' ``` #### Send to Device Send to a specific device. ```bash curl -X POST https://push-api.abrova.ir/push/send \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "deviceId": "dev_abc123def456", "title": "Order Update", "body": "Your order has shipped!", "data": { "orderId": "ORD-789" } }' ``` #### Send to Devices Send to multiple devices by ID. ```bash curl -X POST https://push-api.abrova.ir/push/send-to-devices \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "deviceIds": ["dev_abc123", "dev_def456", "dev_ghi789"], "title": "Batch Notification", "body": "This goes to multiple devices", "data": { "campaign": "promo_2024" } }' ``` #### Send to Topic Send to every subscriber of a topic. ```bash curl -X POST https://push-api.abrova.ir/push/send-to-topic \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "topic": "sports", "title": "Breaking News", "body": "Your team just won!" }' ``` #### Send to Segment Send to every device in a saved [segment](/docs/push/segments). ```bash curl -X POST https://push-api.abrova.ir/push/send-to-segment \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "segmentId": "segment-uuid-123", "title": "Just for you", "body": "A deal for our most active users" }' ``` #### Broadcast Send to every registered device. ```bash curl -X POST https://push-api.abrova.ir/push/broadcast \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "title": "App Update", "body": "New features available! Update now." }' ```

Request Body

Unknown body fields are rejected with `400 Bad Request`. For rich notifications with images, action buttons, deep links, and more, see [Rich Notifications](/docs/push/rich).

Response

Successful sends return the device counts, the generated `messageId` (a UUID) and a billing summary: ```json { "success": 5, "failed": 0, "messageId": "3f1c2a9e-8b7d-4e21-9a0c-5d6e7f8a9b0c", "billing": { "messagesCharged": 5, "remaining": 99995 } } ``` `success` and `failed` are device counts. `billing.remaining` is `-1` when nothing was charged or the remaining quota is unknown. #### No Recipients When the target had zero registered devices the send is **not** treated as a delivery failure. The response carries `reason: "no_recipients"` and both counts are zero so your server can distinguish the two cases without polling the message: ```json { "success": 0, "failed": 0, "messageId": "3f1c2a9e-8b7d-4e21-9a0c-5d6e7f8a9b0c", "reason": "no_recipients", "billing": { "messagesCharged": 0, "remaining": -1 } } ``` The message is recorded with `status: "no_recipients"` and the webhook fires `message.no_recipients` (not `message.failed`).

Error Responses

#### Missing or Invalid Credentials ```json { "statusCode": 401, "message": "API key is required (x-api-key header)" } ``` #### Missing Server Secret ```json { "statusCode": 401, "message": "Server secret is required for this operation (x-server-secret header). Server secret can only be used from your backend server, never from client apps." } ``` #### IP Not in Whitelist ```json { "statusCode": 403, "message": "Access denied: Your IP address (x.x.x.x) is not in the allowed list. Please add your server IP to the allowed IPs in Abrova Console (Project settings)." } ``` - راهنمای Node.js: https://abrova.ir/docs/push/nodejs-guide Server-side SDK for sending push notifications, managing inbox, devices, and more from your Node.js backend. For the per-platform SDK reference page, see [Node.js SDK reference](/docs/push/nodejs).

Installation

The SDK installs from Abrova Center in two steps. 1. Point the `@abrova` scope at Abrova Center in your project's `.npmrc`: ``` # .npmrc (in your project root) @abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` 2. Install the package: ```bash npm install @abrova/push-node ``` #### Initialize ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: process.env.ABROVA_API_KEY, // ab_live_xxx serverSecret: process.env.ABROVA_SERVER_SECRET, // ab_srv_xxx (required, server only) }); ``` #### Quick Example ```typescript await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'Order Shipped', body: 'Your order #1234 has been shipped!', data: { orderId: '1234' }, }); ``` #### Error Handling ```typescript import { AbrovaPush, AbrovaPushError } from '@abrova/push-node'; try { await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'Hello', body: 'World', }); } catch (error) { if (error instanceof AbrovaPushError) { // error.statusCode, error.message } } ```

Security — Authentication

Server-side operations require **both API Key and Server Secret** for authentication. This ensures that only your backend server can perform privileged operations. #### Two Credential Types - **API Key** (`ab_live_xxx`) — Used by client SDKs (iOS, Android, Flutter, Web). Can be embedded in mobile/web apps. - **Server Secret** (`ab_srv_xxx`) — **Required** for all server-side operations. Used by Node.js SDK and direct REST API calls from your backend. **Never expose this in client-side code!** #### Why Both Are Required - API Key alone can be extracted from mobile apps - Server Secret ensures the request comes from your trusted backend - Prevents unauthorized server-side operations even if API key leaks - Simplifies security — no need to decide which operations need extra protection #### Where to Find Your Credentials 1. Go to **Abrova Console** 2. Create a new project (add the **Server / API** platform to get a server secret) or select an existing one 3. The **API Key** and **Server Secret** are shown in full at project creation 4. **Save both immediately** — they are only shown once! 5. If lost (or never created), regenerate / generate them from **Project Settings → Credentials** #### Environment Variables ```bash # .env file ABROVA_API_KEY=ab_live_abc123def456... ABROVA_SERVER_SECRET=ab_srv_xyz789... ``` ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: process.env.ABROVA_API_KEY, serverSecret: process.env.ABROVA_SERVER_SECRET, }); ``` #### Example — Order Update Notification ```typescript // services/notification.service.ts import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: process.env.ABROVA_API_KEY, serverSecret: process.env.ABROVA_SERVER_SECRET, }); export async function notifyOrderShipped(userId: string, orderId: string, trackingUrl: string) { // Send push notification await abrovaPush.push.sendToUser({ userId, title: 'Order Shipped!', body: `Your order #${orderId} is on its way`, data: { orderId, trackingUrl }, deepLink: `myapp://orders/${orderId}`, }); // Also add to inbox for permanent record await abrovaPush.inbox.sendToUser({ userId, content: { title: 'Order Shipped', body: `Your order #${orderId} has been shipped. Track your package.`, deepLink: `myapp://orders/${orderId}`, }, category: 'orders', }); } // Usage in your order controller app.post('/orders/:orderId/ship', async (req, res) => { const { orderId } = req.params; const order = await Order.findById(orderId); order.status = 'shipped'; order.trackingUrl = req.body.trackingUrl; await order.save(); await notifyOrderShipped(order.userId, orderId, order.trackingUrl); res.json({ success: true }); }); ``` #### Client vs Server Authentication

Push — Send Notifications

#### Send to User ```typescript await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'New Message', body: 'You have a new message from John', data: { chatId: 'chat-456' }, deepLink: 'myapp://chat/456', }); ``` #### Send to Device ```typescript await abrovaPush.push.sendToDevice({ deviceId: 'device-uuid', title: 'Welcome!', body: 'Thanks for installing our app', }); ``` #### Send to Multiple Devices ```typescript await abrovaPush.push.sendToDevices({ deviceIds: ['device-1', 'device-2', 'device-3'], title: 'Update Available', body: 'A new version is available', }); ``` #### Send to Topic ```typescript await abrovaPush.push.sendToTopic({ topic: 'promotions', title: 'Flash Sale!', body: '50% off everything today only', imageUrl: 'https://example.com/sale.jpg', }); ``` #### Send to Segment ```typescript await abrovaPush.push.sendToSegment({ segmentId: 'vip-customers-segment-id', title: 'Exclusive Offer', body: 'Just for our VIP customers', }); ``` #### Broadcast ```typescript await abrovaPush.push.broadcast({ title: 'App Update', body: 'Check out the new features!', }); ``` #### Rich Notifications ```typescript await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'New Product', body: 'Check out our latest arrival', imageUrl: 'https://example.com/product.jpg', actions: [ { id: 'buy', title: 'Buy Now', action: 'BUY_PRODUCT' }, { id: 'later', title: 'Save for Later' }, ], deepLink: 'myapp://product/123', badge: 1, sound: 'notification.wav', priority: 'high', ttl: 3600, // 1 hour }); ``` #### Using Templates ```typescript await abrovaPush.push.sendToUser({ userId: 'user-123', templateId: 'order-shipped-template-id', templateVariables: { orderNumber: '#1234', trackingUrl: 'https://track.example.com/1234', }, }); ``` #### App Identifier (Multi-app Targeting) ```typescript // Send only to a specific app on the device await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'New Order', body: 'Your order is ready', appIdentifier: 'com.myshop.app', // only this app receives it }); ``` `appIdentifier` If your project has multiple apps (e.g. a shopping app and a chat app), use appIdentifier to target a specific app. This is the bundle ID / package name of the app (e.g. com.myapp.ios). #### Response ```typescript const result = await abrovaPush.push.sendToUser({...}); console.log(result); // { // success: 3, // failed: 0, // messageId: '6f1c…', // use with abrovaPush.receipts // billing: { // messagesCharged: 3, // messages counted for this send // remaining: 9997 // free messages left this month // } // } ```

Inbox

The read / update calls (`getMessages`, `getMessage`, `updateStatus`, `updateMultiple`, `markAllRead`, `delete`) use the inbox routes the client SDKs call. If the project turns on **Require signed user tokens for the inbox** (Project Settings → Abrova Push), those routes need a user's `x-user-token`, which this SDK does not send, so they return `401` (`token_required`) for user-owned messages. The `send*` / `broadcast` calls are not affected. ```typescript // Send to single user await abrovaPush.inbox.sendToUser({ userId: 'user-123', content: { title: 'Welcome!', body: 'Thanks for joining us. Here are some tips to get started.', imageUrl: 'https://example.com/welcome.jpg', deepLink: 'myapp://onboarding', }, category: 'onboarding', expiresAt: '2025-12-31T23:59:59Z', }); // Send to multiple users await abrovaPush.inbox.sendToUsers({ userIds: ['user-1', 'user-2', 'user-3'], content: { title: 'New Feature', body: 'Check out our latest feature' }, }); // Send to device await abrovaPush.inbox.sendToDevice({ deviceId: 'device-uuid', content: { title: 'Device-specific Message', body: 'This message is for this device only' }, }); // Broadcast await abrovaPush.inbox.broadcast({ content: { title: 'Holiday Sale', body: 'Up to 70% off!' }, category: 'promotions', expiresAt: '2025-01-05T23:59:59Z', }); // Target specific app await abrovaPush.inbox.sendToUser({ userId: 'user-123', appIdentifier: 'com.myshop.app', content: { title: 'Order Shipped', body: 'Your order is on its way!' }, category: 'orders', }); // Get user's inbox — resolves to { messages, total, unreadCount } const inbox = await abrovaPush.inbox.getMessages({ userId: 'user-123', status: 'unread', limit: 20, offset: 0, }); // Mark single message as read await abrovaPush.inbox.updateStatus('message-id', 'read'); // Mark multiple messages as read await abrovaPush.inbox.updateMultiple( ['msg-1', 'msg-2', 'msg-3'], 'read' ); // Mark all as read for a user await abrovaPush.inbox.markAllRead({ userId: 'user-123' }); ```

Devices

```typescript // List devices const devices = await abrovaPush.devices.list(); // Subscribe / unsubscribe to topic await abrovaPush.devices.subscribeTopic('device-uuid', 'news'); await abrovaPush.devices.unsubscribeTopic('device-uuid', 'promotions'); // Associate / clear user await abrovaPush.devices.setUserId('device-uuid', 'user-123'); await abrovaPush.devices.clearUserId('device-uuid'); // Delete devices await abrovaPush.devices.delete('device-uuid'); await abrovaPush.devices.bulkDelete(['device-1', 'device-2', 'device-3']); // Paginated, filtered list (includes inactive devices unless isActive is set) const { items, total } = await abrovaPush.devices.listPage({ platform: 'ios', isActive: true, page: 1, limit: 50, }); // Values you can filter on (app versions, SDK versions, failure reasons) const options = await abrovaPush.devices.filterOptions(); // Stop sending to a device but keep its history, and undo it await abrovaPush.devices.unregister('device-uuid'); await abrovaPush.devices.reactivate('device-uuid'); ```

Templates

Reusable notification content with variable substitution. ```typescript // Create template const template = await abrovaPush.templates.create({ name: 'Order Shipped', title: 'Your order {{orderNumber}} has shipped!', body: 'Track your package: {{trackingUrl}}', variables: ['orderNumber', 'trackingUrl'], localizations: { fa: { title: 'سفارش {{orderNumber}} ارسال شد!', body: 'پیگیری مرسوله: {{trackingUrl}}', }, }, }); // List const templates = await abrovaPush.templates.list(); // Render (preview) const rendered = await abrovaPush.templates.render( 'template-id', { orderNumber: '#1234', trackingUrl: 'https://track.example.com/1234' }, 'en' // locale ); // Update / Delete / Duplicate await abrovaPush.templates.update('template-id', { body: 'Updated body text with {{variable}}', }); await abrovaPush.templates.delete('template-id'); const copy = await abrovaPush.templates.duplicate('template-id'); ```

Segments

```typescript // Create segment const segment = await abrovaPush.segments.create({ name: 'VIP Customers', description: 'Customers with more than 10 orders', color: '#FFD700', filters: [ { field: 'metadata.orderCount', operator: 'greater_than', value: 10 }, { field: 'metadata.tier', operator: 'equals', value: 'gold' }, ], }); ``` #### Filter Operators ```typescript // List, get, devices const segments = await abrovaPush.segments.list(); const segment = await abrovaPush.segments.get('segment-id'); const devices = await abrovaPush.segments.getDevices('segment-id'); // Recalculate const { deviceCount } = await abrovaPush.segments.recalculate('segment-id'); await abrovaPush.segments.recalculateAll(); ```

Scheduled Messages

```typescript // Schedule const scheduled = await abrovaPush.scheduled.create({ title: 'Reminder', body: "Don't forget to complete your purchase!", targetType: 'user', targetValue: 'user-123', scheduledAt: '2025-02-14T09:00:00Z', timezone: 'Asia/Tehran', }); ``` #### Target Types ```typescript // List all scheduled messages const all = await abrovaPush.scheduled.list(); // List pending only const pending = await abrovaPush.scheduled.listPending(); // Get specific scheduled message const message = await abrovaPush.scheduled.get('scheduled-id'); // Update scheduled message await abrovaPush.scheduled.update('scheduled-id', { scheduledAt: '2025-02-15T10:00:00Z', }); // Cancel scheduled message await abrovaPush.scheduled.cancel('scheduled-id'); // Delete scheduled message await abrovaPush.scheduled.delete('scheduled-id'); ```

Webhooks

```typescript // Create webhook const webhook = await abrovaPush.webhooks.create({ name: 'Order Events', url: 'https://example.com/webhooks/abrova-push', events: [ 'message.sent', 'message.delivered', 'message.failed', ], secret: 'my-webhook-secret', maxRetries: 3, timeoutMs: 5000, }); ``` #### Available Events ```typescript // Test, get logs, regenerate secret await abrovaPush.webhooks.test('webhook-id', 'message.delivered'); const logs = await abrovaPush.webhooks.getLogs('webhook-id', 50); const { secret } = await abrovaPush.webhooks.regenerateSecret('webhook-id'); // Manage const webhooks = await abrovaPush.webhooks.list(); await abrovaPush.webhooks.update('webhook-id', { events: ['message.delivered', 'message.failed'], isActive: true, }); await abrovaPush.webhooks.delete('webhook-id'); ```

Analytics

```typescript // Overview stats const overview = await abrovaPush.analytics.getOverview(); // overview: { // totalDevices: 15000, // activeDevices: 12500, // totalMessages: 50000, // totalDelivered: 48500, // totalFailed: 500, // deliveryRate: 97, // platformDistribution: { android: 8000, ios: 6500, web: 500 } // } // Get last 30 days of stats const daily = await abrovaPush.analytics.getDaily(30); // daily: [ // { date: '2025-02-01', sent: 1500, delivered: 1450, pending: 20, failed: 30 }, // { date: '2025-02-02', sent: 1600, delivered: 1550, pending: 10, failed: 40 }, // ... // ] // Get hourly distribution for last 7 days const hourly = await abrovaPush.analytics.getHourly(7); // hourly: [ // { hour: 0, count: 120 }, // { hour: 1, count: 80 }, // ... // { hour: 23, count: 150 } // ] // App-level stats const stats = await abrovaPush.analytics.getStats(); // stats: { // totalDevices: 15000, // activeDevices: 12500, // androidDevices: 8000, // iosDevices: 6500, // totalTopics: 25, // topics: ['news', 'promotions', 'updates', ...], // totalUsers: 10000 // } // Recent messages const recent = await abrovaPush.analytics.getRecent(10); ``` #### Delivery receipts A send's `success` count means the push service *accepted* the notification. Receipts show what happened on each device. See [Delivery Receipts](/docs/push/receipts). ```typescript const { messageId } = await abrovaPush.push.sendToUser({ userId: 'user-123', title: 'Hi', body: 'Hello' }); // Per device, for one message const receipts = await abrovaPush.receipts.getForMessage(messageId!); const stats = await abrovaPush.receipts.getMessageStats(messageId!); // Receipt log with filters const { items, total } = await abrovaPush.receipts.list({ status: 'failed', platform: 'android', startDate: new Date(Date.now() - 7 * 86400_000), page: 1, limit: 100, }); // Delivery analytics (default: last 30 days) const analytics = await abrovaPush.receipts.analytics({ bucket: 'day' }); console.log(analytics.totals, analytics.byPlatform, analytics.topFailureReasons); ``` - آمار: https://abrova.ir/docs/push/analytics Track push notification performance with detailed analytics. Every analytics endpoint is available both through the Node.js SDK and the REST API. These endpoints return data for the whole project, so they need the server secret (`x-server-secret`) as well as the API key; the API key alone is refused.

Total

Messages sent

Delivered

Successfully

Failed

Delivery failures

Rate

Success %

Get Overview

Get overall statistics for your push notifications. #### Node.js SDK ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serverSecret: 'ab_srv_your_server_secret', }); const overview = await abrovaPush.analytics.getOverview(); ``` #### REST API ```bash curl -X GET "https://push-api.abrova.ir/analytics/overview" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json { "totalMessages": 15420, "totalDelivered": 14890, "totalFailed": 530, "totalPending": 0, "totalNoTargets": 12, "totalAttempts": 3210, "deliveryRate": 97, "totalDevices": 2500, "activeDevices": 2100, "platformDistribution": { "android": 1400, "ios": 900, "web": 200 } } ```

Daily Statistics

Get message statistics broken down by day. `days` defaults to 30 (max 90); days with no sends are returned with zeros. `sent` counts messages (API sends), while `delivered` and `failed` count individual devices. #### Node.js SDK ```typescript const daily = await abrovaPush.analytics.getDaily(30); ``` #### REST API ```bash curl -X GET "https://push-api.abrova.ir/analytics/daily?days=30" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json [ { "date": "2024-01-15", "sent": 12, "delivered": 510, "pending": 0, "failed": 10 }, { "date": "2024-01-16", "sent": 9, "delivered": 475, "pending": 0, "failed": 5 } // ... more days ] ```

Message Type Breakdown

See how messages are distributed by send type (user / device / topic / broadcast). `percentage` is rounded to a whole number. #### Node.js SDK ```typescript const messageTypes = await abrovaPush.analytics.getMessageTypes(); ``` #### REST API ```bash curl -X GET "https://push-api.abrova.ir/analytics/message-types" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json [ { "type": "user", "count": 8500, "percentage": 55 }, { "type": "topic", "count": 4200, "percentage": 27 }, { "type": "broadcast", "count": 2000, "percentage": 13 }, { "type": "device", "count": 720, "percentage": 5 } ] ```

Hourly Distribution

Analyze message distribution by hour of day (UTC). `days` defaults to 7 (max 30); all 24 hours are always returned. #### Node.js SDK ```typescript const hourly = await abrovaPush.analytics.getHourly(7); ``` #### REST API ```bash curl -X GET "https://push-api.abrova.ir/analytics/hourly?days=7" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json [ { "hour": 0, "count": 120 }, { "hour": 1, "count": 85 }, { "hour": 2, "count": 45 }, // ... { "hour": 12, "count": 650 }, { "hour": 13, "count": 580 }, // ... { "hour": 23, "count": 180 } ] ```

Recent Activity

Get the most recent messages sent. `limit` defaults to 10 (max 50). Each item is a full message record. #### Node.js SDK ```typescript const recent = await abrovaPush.analytics.getRecent(10); ``` #### REST API ```bash curl -X GET "https://push-api.abrova.ir/analytics/recent?limit=10" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "type": "user", "target": "user_123", "title": "New message", "body": "You have a new notification", "successCount": 2, "failedCount": 0, "status": "delivered", "createdAt": "2024-01-15T10:30:00Z" }, { "id": "6fa459ea-ee8a-3ca4-894e-db77e160355e", "type": "device", "target": "device_abc", "title": "Order update", "body": "Your order has shipped", "successCount": 1, "failedCount": 0, "status": "delivered", "createdAt": "2024-01-15T10:25:00Z" } ] ``` - زمان‌بندی: https://abrova.ir/docs/push/scheduled Schedule push notifications for delivery at a specific future time.

Use Cases

- **Marketing campaigns** — send at optimal engagement times - **Event reminders** — notify before appointments - **Time-sensitive content** — flash sales, limited offers - **Global audiences** — schedule separate messages for each region's local time

Schedule a Message

`scheduledAt` is an ISO 8601 timestamp and must be in the future; it is the exact instant the message is sent. `timezone` is optional (defaults to `UTC`) and is stored with the message for display only — it does not shift `scheduledAt`, so include the offset (or use `Z`) in `scheduledAt` itself. Due messages are picked up once a minute. Only `title`, `body` and `data` are delivered with a scheduled message. #### Node.js SDK ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serverSecret: 'ab_srv_your_server_secret', }); const scheduled = await abrovaPush.scheduled.create({ title: 'Flash Sale!', body: '50% off for the next 2 hours', targetType: 'broadcast', scheduledAt: '2024-01-20T10:00:00Z', timezone: 'Asia/Tehran', }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/scheduled" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "title": "Flash Sale!", "body": "50% off for the next 2 hours", "targetType": "broadcast", "scheduledAt": "2024-01-20T10:00:00Z", "timezone": "Asia/Tehran" }' ```

Target Types

Target Specific Devices/Users

#### Node.js SDK ```typescript await abrovaPush.scheduled.create({ title: 'Your appointment reminder', body: "Don't forget your meeting in 1 hour", targetType: 'user', targetValue: 'user_123', scheduledAt: '2024-01-20T09:00:00Z', timezone: 'Asia/Tehran', }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/scheduled" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "title": "Your appointment reminder", "body": "Dont forget your meeting in 1 hour", "targetType": "user", "targetValue": "user_123", "scheduledAt": "2024-01-20T09:00:00Z", "timezone": "Asia/Tehran" }' ```

List Scheduled Messages

#### Node.js SDK ```typescript const messages = await abrovaPush.scheduled.list(); // Or only pending const pending = await abrovaPush.scheduled.listPending(); ``` #### REST API ```bash curl -X GET "https://push-api.abrova.ir/scheduled" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json [ { "id": "sch_abc123", "title": "Flash Sale!", "body": "50% off for 2 hours", "targetType": "broadcast", "scheduledAt": "2024-01-20T10:00:00Z", "timezone": "Asia/Tehran", "status": "pending", "createdAt": "2024-01-15T12:00:00Z" } ] ```

Message Status

Cancel a Scheduled Message

Cancel a pending message before it's sent. Only messages with status `pending` can be cancelled (or updated with `PUT /scheduled/:id` / `abrovaPush.scheduled.update(id, {...})`). #### Node.js SDK ```typescript await abrovaPush.scheduled.cancel('sch_abc123'); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/scheduled/sch_abc123/cancel" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ```

Delete a Scheduled Message

#### Node.js SDK ```typescript await abrovaPush.scheduled.delete('sch_abc123'); ``` #### REST API ```bash curl -X DELETE "https://push-api.abrova.ir/scheduled/sch_abc123" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` - قالب‌ها: https://abrova.ir/docs/push/templates Create reusable notification templates with dynamic variables and localizations. Define a template once, send it many times with different variables.

Overview

Templates let you define reusable notification content with placeholders for dynamic data. Instead of hardcoding notification text in your backend, create a template once and send it with different variables each time. Templates support localization: store per-locale copy and pick the language with `locale` when you send.

Dynamic Variables

Use {`{{variable}}`} placeholders that get replaced at send time with real data

Localization

Define per-locale title and body; choose the locale per send

Reusable

One template, many sends. Update the template and all future sends use the new content

Variable Syntax

Use double curly braces to insert dynamic content: `{{variableName}}` (letters, digits and underscores). Variables are automatically detected from your template's title and body unless you pass an explicit `variables` array. **Title:** `Hello {{username}}!` **Body:** `Your order #{{orderId}} has been shipped to {{address}}` Detected variables: `username`, `orderId`, `address`. Variable names are case-sensitive. `{{UserName}}` and `{{username}}` are different variables. If a variable is not provided at send time, the placeholder is left in the text as-is (e.g. `{{username}}`).

Create a Template

#### Node.js SDK ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serverSecret: 'ab_srv_your_server_secret', }); const template = await abrovaPush.templates.create({ name: 'Order Shipped', title: 'Order #{{orderId}} Shipped!', body: 'Hi {{username}}, your order is on the way to {{address}}.', description: 'Sent when an order ships', }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/templates" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "name": "Order Shipped", "title": "Order #{{orderId}} Shipped!", "body": "Hi {{username}}, your order is on the way to {{address}}.", "description": "Sent when an order ships" }' ``` #### Response ```json { "id": "tpl_abc123", "name": "Order Shipped", "title": "Order #{{orderId}} Shipped!", "body": "Hi {{username}}, your order is on the way to {{address}}.", "description": "Sent when an order ships", "variables": ["orderId", "username", "address"], "createdAt": "2024-01-15T10:00:00Z" } ```

Send Using a Template

Pass the template ID and a `templateVariables` object when sending (any `send*` / `broadcast` call). AbrovaPush renders the template server-side and delivers the final notification. A `title`, `body` or `data` you pass in the same request overrides the template's value. #### Node.js SDK ```typescript await abrovaPush.push.sendToUser({ userId: 'user_123', templateId: 'tpl_abc123', templateVariables: { orderId: 'ORD-12345', username: 'John', address: '123 Main St', }, }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/push/send-to-user" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_123", "templateId": "tpl_abc123", "templateVariables": { "orderId": "ORD-12345", "username": "John", "address": "123 Main St" } }' ``` #### Rendered Result
📦
Your App now

Order #ORD-12345 Shipped!

Hi John, your order is on the way to 123 Main St.

Localization

Templates support per-locale overrides. Pass `locale` in the send request (e.g. `"locale": "fa"`) and AbrovaPush uses the localization stored under that exact key. If `locale` is omitted or has no match, the default title and body are used. The locale is chosen per request, not per recipient device — send one request per language to reach a mixed audience. ```bash curl -X POST "https://push-api.abrova.ir/templates" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "name": "Order Shipped", "title": "Order #{{orderId}} Shipped!", "body": "Hi {{username}}, your order is on the way.", "localizations": { "fa": { "title": "سفارش #{{orderId}} ارسال شد!", "body": "سلام {{username}}، سفارش شما در راه است." }, "es": { "title": "Pedido #{{orderId}} enviado!", "body": "Hola {{username}}, tu pedido va en camino." } } }' ``` Use standard locale codes (`en`, `fa`, `es`, `fr`, `de`, etc.) — the key must match the `locale` you send exactly. The same `{{variables}}` work across all locales.

Custom Data Payload

Besides template variables (which control notification text), you can attach a `data` object that gets delivered alongside the notification. Useful for deep linking, navigation, or passing metadata to your app. ```bash curl -X POST "https://push-api.abrova.ir/push/send-to-user" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_123", "templateId": "tpl_abc123", "templateVariables": { "orderId": "ORD-12345", "username": "John" }, "data": { "screen": "order_detail", "orderId": "ORD-12345", "action": "view_tracking" } }' ``` The `data` payload is not rendered in the notification text — it's passed through to your app when the user taps the notification. Use it for routing, deep linking, or storing context. A template can also store a default `data` object; a `data` object in the send request replaces it entirely (it is not merged, and variables are not substituted inside `data`).

Manage Templates

#### List Templates (SDK) ```typescript const templates = await abrovaPush.templates.list(); ``` #### List Templates (REST API) ```bash curl -X GET "https://push-api.abrova.ir/templates" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Update a Template (SDK) ```typescript const updated = await abrovaPush.templates.update('tpl_abc123', { title: 'Your order #{{orderId}} is on the way!', body: 'Hi {{username}}, track it here: {{trackingUrl}}', }); ``` #### Update a Template (REST API) ```bash curl -X PUT "https://push-api.abrova.ir/templates/tpl_abc123" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "title": "Your order #{{orderId}} is on the way!", "body": "Hi {{username}}, track it here: {{trackingUrl}}" }' ``` #### Delete a Template (SDK) ```typescript await abrovaPush.templates.delete('tpl_abc123'); ``` #### Delete a Template (REST API) ```bash curl -X DELETE "https://push-api.abrova.ir/templates/tpl_abc123" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` Deleting a template is permanent. Any service or integration that references this template ID will fail to send notifications until updated with a new template ID.

Example Templates

Common templates for different industries. Use these as starting points for your own notifications.

Welcome Message

Onboarding

Title: Welcome, {`{{name}}`}!

Body: Thanks for joining. Start exploring at {`{{appUrl}}`}

Variables: name, appUrl

Order Shipped

Notifications

Title: Order #{`{{orderId}}`} shipped!

Body: Hi {`{{username}}`}, your package is on the way to {`{{address}}`}.

Variables: orderId, username, address

Payment Received

Billing

Title: Payment of {`{{amount}}`} received

Body: Your payment for invoice #{`{{invoiceId}}`} was successful.

Variables: amount, invoiceId

Event Reminder

Scheduling

Title: Reminder: {`{{eventName}}`} tomorrow

Body: Hi {`{{userName}}`}, your meeting with {`{{hostName}}`} is at {`{{time}}`}.

Variables: eventName, userName, hostName, time

New Assignment

Education

Title: New assignment in {`{{courseName}}`}

Body: {`{{teacherName}}`} posted "{`{{assignmentTitle}}`}". Due: {`{{dueDate}}`}

Variables: courseName, teacherName, assignmentTitle, dueDate

Delivery Update

Food Delivery

Title: Your order from {`{{restaurantName}}`}

Body: {`{{driverName}}`} is on the way! Estimated arrival: {`{{eta}}`}

Variables: restaurantName, driverName, eta

Using Templates with Other Services

AbrovaPush templates work with any service that integrates with the AbrovaPush API. Define the template once and any service can send notifications using it by passing the template ID and variables. 1. Create a template in AbrovaPush (dashboard or API) 2. Copy the template ID (e.g. `tpl_abc123`) 3. Configure the template ID in your service (e.g. Abrova Chat, your backend) 4. The service calls AbrovaPush with the template ID + `templateVariables` (and optional `locale`) at the right moment 5. AbrovaPush renders the template, applies localization, and delivers the push notification

Best Practices

- بخش‌ها: https://abrova.ir/docs/push/segments Create dynamic groups based on device attributes for targeted messaging.

Available Filter Fields

platform

android, ios, web

userId

User identifier

appVersion

App version string

country

Country code

language

Device language

metadata.*

Custom fields

Create a Segment

#### Node.js SDK ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serverSecret: 'ab_srv_your_server_secret', }); const segment = await abrovaPush.segments.create({ name: 'Premium iOS Users', description: 'iOS users with premium subscription', filters: [ { field: 'platform', operator: 'equals', value: 'ios' }, { field: 'metadata.tier', operator: 'equals', value: 'premium' }, ], }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/segments" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "name": "Premium iOS Users", "description": "iOS users with premium subscription", "filters": [ { "field": "platform", "operator": "equals", "value": "ios" }, { "field": "metadata.tier", "operator": "equals", "value": "premium" } ] }' ``` #### Response ```json { "id": "seg_abc123", "name": "Premium iOS Users", "description": "iOS users with premium subscription", "color": "#6366f1", "filters": [ { "field": "platform", "operator": "equals", "value": "ios" }, { "field": "metadata.tier", "operator": "equals", "value": "premium" } ], "deviceCount": 850, "createdAt": "2024-01-15T10:00:00Z" } ```

Filter Operators

18)'], ['less_than', 'Numeric less than (metadata.age < 65)'], ['in', 'In list of values (country in ["US", "CA", "GB"])'], ['not_in', 'Not in list of values'], ['exists', 'metadata.* key is present (value true) or missing (value false) — metadata.vip exists true'], ['starts_with', 'String starts with — top-level fields only (not metadata.*)'], ['greater_than_or_equal', 'Greater than or equal — top-level fields only (not metadata.*)'], ['less_than_or_equal', 'Less than or equal — top-level fields only (not metadata.*)'], ]} /> Filters are combined with AND. Comparisons on `appVersion` are version-aware (`2.10.0` > `2.9.0`), and `platform` values are matched in lowercase. `starts_with`, `greater_than_or_equal` and `less_than_or_equal` are not in the Node SDK's `SegmentFilterOperator` type yet. The dashboard **auto-discovers** which `metadata.*` keys and values your devices actually send — the segment builder suggests real keys from your project, and operator choices adapt to each key's inferred type (string, number, boolean). You can still type any custom `metadata.*` key you plan to write from your SDK later.

Example Segments

#### Android Users in US/Canada ```json { "name": "North American Android", "filters": [ { "field": "platform", "operator": "equals", "value": "android" }, { "field": "country", "operator": "in", "value": ["US", "CA"] } ] } ``` #### VIP Users (Custom Metadata) ```json { "name": "VIP Members", "filters": [ { "field": "metadata.vip", "operator": "equals", "value": true }, { "field": "metadata.totalSpent", "operator": "greater_than", "value": 1000 } ] } ``` #### Beta Testers on Latest Version ```json { "name": "Beta Testers", "filters": [ { "field": "metadata.betaTester", "operator": "equals", "value": true }, { "field": "appVersion", "operator": "contains", "value": "3." } ] } ```

Send to Segment

Send to a segment with an immediate push. Scheduled messages can't target segments; send to a segment immediately. #### Node.js SDK ```typescript // Send to segment immediately await abrovaPush.push.sendToSegment({ segmentId: 'seg_abc123', title: 'Exclusive for Premium!', body: 'Check out your member benefits', }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/push/send-to-segment" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "segmentId": "seg_abc123", "title": "Exclusive for Premium!", "body": "Check out your member benefits" }' ```

Preview Segment

Get the match count + a small device sample for a set of filters **without saving** them. Useful for validating filter logic before creating a segment. The dashboard's segment builder shows this preview live in the right panel as you edit — no code needed there. The API + SDK below are for programmatic use (CI checks, batch jobs, custom UIs). #### Node.js SDK ```typescript const { count, preview } = await abrovaPush.segments.preview({ filters: [ { field: 'platform', operator: 'equals', value: 'ios' }, { field: 'metadata.tier', operator: 'equals', value: 'premium' }, ], limit: 10, // optional, default 20, max 50 }); console.log(`${count} devices match`); preview.forEach((d) => console.log(d.deviceId, d.userId)); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/segments/preview?limit=10" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "filters": [ { "field": "platform", "operator": "equals", "value": "ios" }, { "field": "metadata.tier", "operator": "equals", "value": "premium" } ] }' ``` #### Response ```json { "count": 1250, "preview": [ { "deviceId": "dev_abc...", "platform": "ios", "userId": "user_1", "topics": ["news", "promos"], "metadata": { "tier": "premium", "country": "US" } }, { "deviceId": "dev_def...", "platform": "ios", "userId": "user_2", "topics": null, "metadata": { "tier": "premium" } } ] } ``` Long metadata string values are truncated to 200 characters (with an ellipsis) in the preview response to keep payloads small. - وب‌هوک: https://abrova.ir/docs/push/webhooks Receive real-time HTTP callbacks when events occur.

Available Events

message.sent

A send request was accepted for delivery (fired once per message, with success/failed counts)

message.delivered

A device confirmed receipt (fired once per device)

message.failed

The send failed (fired once per message)

message.no_recipients

No device matched the target — message not attempted

device.registered

New device registered

device.updated

Existing device re-registered or re-activated

device.unregistered

Device unregistered or deactivated

Create a Webhook

#### Node.js SDK ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serverSecret: 'ab_srv_your_server_secret', }); const webhook = await abrovaPush.webhooks.create({ name: 'Analytics Webhook', url: 'https://your-server.com/webhooks/abrova-push', events: ['message.sent', 'message.delivered', 'message.failed'], }); console.log('Webhook secret:', webhook.secret); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/webhooks" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "name": "Analytics Webhook", "url": "https://your-server.com/webhooks/abrova-push", "events": ["message.sent", "message.delivered", "message.failed"] }' ``` #### Response ```json { "id": "wh_abc123", "name": "Analytics Webhook", "url": "https://your-server.com/webhooks/abrova-push", "events": ["message.sent", "message.delivered", "message.failed"], "secret": "whsec_xyz789...", "isActive": true, "maxRetries": 3, "timeoutMs": 5000, "createdAt": "2024-01-15T10:00:00Z" } ``` Save the `secret` value — you'll need it to verify webhook signatures. If you don't pass your own `secret`, one is generated (`whsec_...`). You can rotate it with `POST /webhooks/:id/regenerate-secret` (`abrovaPush.webhooks.regenerateSecret(id)`).

Webhook Payload

When an event occurs, we send a POST request to your URL with the headers `X-AbrovaPush-Signature`, `X-AbrovaPush-Event` and `X-AbrovaPush-Webhook-Id`, and this payload: ```json { "event": "message.sent", "timestamp": "2024-01-15T10:30:00Z", "webhookId": "wh_abc123", "data": { "messageId": "msg_xyz789", "type": "user", "target": "user_456", "title": "Hello!", "body": "Test message", "successCount": 2, "failedCount": 0, "status": "sent" } } ``` The `data` object depends on the event: - `message.sent` / `message.failed` / `message.no_recipients` — `messageId`, `type`, `target`, `title`, `body`, `successCount`, `failedCount`, `status` - `message.delivered` — `messageId`, `deviceId`, `title` - `device.registered` / `device.updated` — `deviceId`, `platform`, `userId`, `appIdentifier`, `isActive` (or just `deviceId`, `isActive` when a device is re-activated) - `device.unregistered` — `deviceId` (plus `isActive: false` when deactivated)

Verify Webhook Signature

Always verify the `X-AbrovaPush-Signature` header to ensure the webhook came from AbrovaPush. ```javascript const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // Express.js example app.post('/webhooks/abrova-push', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-abrovapush-signature']; const payload = req.body.toString(); if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(payload); switch (event.event) { case 'message.sent': // Record send event break; case 'message.delivered': // Record delivery break; case 'message.failed': // Handle failure break; case 'message.no_recipients': // Target had no registered device — mark the user as push-unreachable, // fall back to email/SMS, etc. Not a delivery failure. break; } res.status(200).send('OK'); }); ```

Retry Policy

If your endpoint returns a non-2xx status code or times out (default timeout 5 seconds, configurable with `timeoutMs` from 1000 to 30000), we'll retry with exponential backoff. With the default `maxRetries` of 3: - **Attempt 1**: Immediate - **Attempt 2**: After 1 second - **Attempt 3**: After 2 seconds - **Attempt 4**: After 4 seconds After 4 failed attempts, the webhook delivery is marked as failed. Set `maxRetries` (1–10) when creating or updating the webhook to change the number of retries. Every attempt is logged — fetch the log with `GET /webhooks/:id/logs?limit=50` (max 100) or `abrovaPush.webhooks.getLogs(id)`.

List Webhooks

#### Node.js SDK ```typescript const webhooks = await abrovaPush.webhooks.list(); ``` #### REST API ```bash curl -X GET "https://push-api.abrova.ir/webhooks" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ```

Update Webhook

#### Node.js SDK ```typescript await abrovaPush.webhooks.update('wh_abc123', { name: 'Updated Webhook', events: ['message.sent', 'device.registered'], isActive: true, }); ``` #### REST API ```bash curl -X PUT "https://push-api.abrova.ir/webhooks/wh_abc123" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Webhook", "events": ["message.sent", "device.registered"], "isActive": true }' ```

Delete Webhook

#### Node.js SDK ```typescript await abrovaPush.webhooks.delete('wh_abc123'); ``` #### REST API ```bash curl -X DELETE "https://push-api.abrova.ir/webhooks/wh_abc123" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ```

Test Webhook

Send a test event to verify your webhook is configured correctly. The test delivery uses the `message.sent` event unless you pass another one (`{ "event": "device.registered" }` in the body, or `abrovaPush.webhooks.test(id, 'device.registered')`), and its `data` is `{ "test": true, "timestamp": "...", "message": "This is a test webhook delivery" }`. The response is the delivery log entry (`success`, `responseStatus`, `error`, `durationMs`, ...). #### Node.js SDK ```typescript const result = await abrovaPush.webhooks.test('wh_abc123'); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/webhooks/wh_abc123/test" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` - آزمایش A/B: https://abrova.ir/docs/push/ab-testing Test different push notification variants to optimize engagement and conversion rates. Compare up to 10 variants with statistical significance analysis. A/B testing, control groups, early stopping and the statistics are included with Abrova Push. Every variant you send counts toward your monthly messages like any other push: 10,000 messages a month are free, and the price above that is set when Abrova Push launches.

Overview

AbrovaPush A/B testing lets you send different notification variants to different user segments and measure which performs best. Tests are created and run in Abrova Console.

Key Features

  • • Up to 10 variants per test
  • • Control groups (no message sent)
  • • Early stopping when significance reached
  • • Statistical confidence analysis
  • • Automatic click tracking on mobile
  • • Target by segment or percentage

Tracked Metrics

  • • Sent — Messages dispatched
  • • Delivered — Successfully received
  • • Opened — Notification viewed
  • • Clicked — User tapped notification
  • • Converted — Custom goal achieved

Creating Tests via Dashboard

Create A/B tests in the [AbrovaPush A/B Testing dashboard](https://console.abrova.ir/dashboard/abrova-push/ab-testing). 1. **Create New Test** — Click "Create A/B Test" and enter test name and description 2. **Configure Variants** — Add 2-10 variants with different titles, bodies, images, and action buttons. Set traffic percentage for each 3. **Set Target Audience** — Choose "All users", a specific segment, or a percentage of your audience 4. **Configure Options** — Enable control group, early stopping, select winning metric, set test duration 5. **Start Test** — Review and start. Send notifications to begin collecting data

Control Groups

A control group receives no notification, letting you measure the true impact of your messaging versus no messaging at all. **Example:** If your control group has 5% conversion and Variant A has 12%, the notification drove a 7% lift — not just that 12% of users converted.

Early Stopping

When enabled, the test automatically completes when statistical significance is reached, saving you from running tests longer than necessary.

SDK Integration — Auto Click Tracking

Mobile SDKs support A/B testing with automatic click tracking. When users tap A/B test notifications, clicks are tracked without additional code. #### Android (Kotlin) ```kotlin // Get variant for a test (with caching) AbrovaPush.getABTestVariant( testId = "test_id", forceRefresh = false, onSuccess = { variant -> // Display content based on variant showBanner( title = variant.content?.title ?: "Default", backgroundColor = variant.content?.data?.get("backgroundColor") ) // Track that variant was displayed AbrovaPush.trackABTestDisplay(variant) }, onError = { error -> /* handle */ } ) // Track click manually (automatic for push notifications) AbrovaPush.trackABTestClicked(testId, variantId) // Track conversion AbrovaPush.trackABTestConverted(testId, variantId) ``` #### iOS (Swift) ```swift // Get variant for a test AbrovaPush.shared.getABTestVariant(testId: "test_id") { result in switch result { case .success(let variant): showBanner( title: variant.content?.title ?? "Default", color: variant.content?.data?["backgroundColor"]?.value as? String ) AbrovaPush.shared.trackABTestDisplay(variant: variant) case .failure(let error): // handle } } // Track click manually (automatic for push notifications) AbrovaPush.shared.trackABTestClicked(testId: testId, variantId: variantId) // Note: the iOS SDK has no conversion-tracking method yet // Handle notification tap (automatic click tracking) func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // This automatically tracks A/B test clicks AbrovaPush.shared.handleNotificationResponse( userInfo: response.notification.request.content.userInfo, actionIdentifier: response.actionIdentifier ) completionHandler() } ``` #### Flutter ```dart final variant = await AbrovaPush.getABTestVariant('test_id'); if (variant != null) { showBanner( title: variant.content?.title ?? 'Default', color: variant.content?.data?['backgroundColor'], ); await AbrovaPush.trackABTestDisplay(variant); } await AbrovaPush.trackABTestClicked(testId, variantId); // Note: the Flutter SDK has no conversion-tracking method yet ``` #### React Native ```typescript const variant = await AbrovaPush.getABTestVariant('test_id'); if (variant) { showBanner({ title: variant.content?.title ?? 'Default', color: variant.content?.data?.backgroundColor, }); await AbrovaPush.trackABTestDisplay(variant); } await AbrovaPush.trackABTestClicked(testId, variantId); // Note: trackABTestConverted is declared in the React Native package but its // native method is missing, so calling it fails. Conversions are Android-only today. ```

Automatic Click Tracking

When you send A/B test notifications from the dashboard, the SDK automatically tracks clicks when users tap on the notification or action buttons. No additional code required. **How it works:** A/B test notifications include `abTestId` and `variantId` in the payload. When the user taps the notification, the SDK extracts these values and sends a click event to the server automatically.

Winner Metrics

Best Practices

- تیم‌ها: https://abrova.ir/docs/push/teams Collaborate with your team by inviting members with role-based access control. Team members are managed per **organisation and project** in Abrova Console (**Members** in the sidebar), not per service — a member added to a project has access to Abrova Push and every other service in it. There is no team-management API in the `@abrova/push-node` SDK, and the API key / server secret cannot manage members.

Roles & Permissions

Invite a Team Member

1. Open **Members** in the Abrova Console sidebar (requires Admin or Owner). 2. Click **Add Member** and choose the project. 3. Enter the email (and optionally a name), pick a role — **Admin**, **Member** or **Viewer** — and submit. If the email doesn't belong to an existing Abrova account, the Console shows a one-time **setup link** you share with the person so they can set a password and access the project.

List Team Members

The **Members** page lists everyone in your organisations with their organisation role, the projects they can access (with per-project role), and pending invites. Use the **All / Org Members / Project-Only / Pending** tabs to filter.

Update Member Role

In **Members → Add Member**, select the project to see its current members, then change a member's role from the role dropdown. Only Admins and Owners can change roles; a project Owner's role cannot be changed there.

Remove Team Member

Admins and Owners can remove a member from a project from the **Members** page (or from the project's member list in the **Add Member** dialog). Owners can't be removed there, and you can't remove yourself.

Transfer Ownership

Transferring ownership is irreversible. The new owner will have full control including the wallet. Only the current Owner can transfer ownership of an organisation. Contact support to transfer ownership.

Team Limits

Abrova has no plans or tiers to compare: adding team members does not cost anything from your wallet. If your organisation reaches a member limit, the Console tells you when you try to add a member; [contact us](/contact) if you need more. - محدودیت نرخ: https://abrova.ir/docs/push/rate-limits Understand API rate limits to ensure fair usage and service stability.

General Limits

Limits are counted per endpoint and per client IP. Unless an endpoint below sets its own per-second limit, these defaults apply to every API request:

SDK Endpoint Rate Limits

Rate limits for SDK endpoints (using API key authentication). #### Push Notifications (server: API key + server secret) #### Device Management #### Delivery Receipts #### Inbox #### In-App Messages #### A/B Testing

Rate Limit Headers

Every API response includes headers for each window, suffixed with the window name (`-short` = per second, `-medium` = per minute, `-long` = per hour), e.g. `X-RateLimit-Remaining-short`:

Rate Limit Exceeded Response

When you exceed the rate limit, you will receive a 429 Too Many Requests response: ```json { "statusCode": 429, "message": "Too Many Requests" } ```

Best Practices

Handling Rate Limits in Code

```typescript async function sendWithRetry(payload: any, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch('https://push-api.abrova.ir/push/send', { method: 'POST', headers: { 'x-api-key': 'ab_live_your_api_key', 'x-server-secret': 'ab_srv_your_server_secret', 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (response.status === 429) { const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { if (attempt === maxRetries - 1) throw error; } } } ``` - نوتیفیکیشن غنی: https://abrova.ir/docs/push/rich Enhance your notifications with images, action buttons, and custom sounds.

Send Rich Notification

#### Node.js SDK ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serverSecret: 'ab_srv_your_server_secret', }); await abrovaPush.push.sendToUser({ userId: 'user_123', title: 'New Product Available!', body: 'Check out our latest arrival', imageUrl: 'https://example.com/product.jpg', iconUrl: 'https://example.com/icon.png', actions: [ { id: 'view', title: 'View Product', action: 'VIEW_PRODUCT' }, { id: 'dismiss', title: 'Not Interested', action: 'DISMISS' } ], data: { productId: 'prod_123', deepLink: '/products/prod_123' } }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/push/send-to-user" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_123", "title": "New Product Available!", "body": "Check out our latest arrival", "imageUrl": "https://example.com/product.jpg", "iconUrl": "https://example.com/icon.png", "actions": [ { "id": "view", "title": "View Product", "action": "VIEW_PRODUCT" }, { "id": "dismiss", "title": "Not Interested", "action": "DISMISS" } ], "data": { "productId": "prod_123", "deepLink": "/products/prod_123" } }' ``` These rich notification fields work with all send endpoints: `send` (one device), `send-to-user`, `send-to-devices`, `send-to-topic`, `send-to-segment`, and `broadcast`. See [REST API](/docs/push/send) for all endpoints.

Rich Notification Fields

Action Button Structure

Each action needs an `id` and a `title` (max 20 characters). `action` is optional: Android passes it to your app as the deep link of the tap, and the Web service worker opens it as a URL. `icon` (Android icon name), `destructive` and `authRequired` (iOS) are optional too. ```json { "actions": [ { "id": "reply", "title": "Reply", "action": "REPLY_ACTION", "icon": "reply_icon" }, { "id": "archive", "title": "Archive", "action": "ARCHIVE_ACTION", "destructive": true } ] } ```

Platform Support

iOS

  • Large images on notifications the SDK shows itself (foreground / real-time connection); background APNs notifications show no image, as the SDK's Notification Service Extension does not download it
  • Up to 3 action buttons
  • Badge number support
  • Thread grouping
  • Custom sounds (must be in app bundle)

Android

  • Big picture style notifications
  • Up to 3 action buttons
  • Large image shown as the large icon when collapsed
  • Notification channels
  • Custom sounds (file name of a res/raw resource)

Handling Actions in SDKs

#### Flutter ```dart import 'package:abrova_push/abrova_push.dart'; AbrovaPush.onNotificationAction((event) { print('Action tapped: ${event.actionId}'); print('Notification title: ${event.title}'); print('Notification data: ${event.data}'); switch (event.actionId) { case 'view': Navigator.pushNamed(context, '/product/${event.data?['productId']}'); break; case 'dismiss': // Handle dismiss break; } }); ``` #### Android Override `onNotificationAction` in your `AbrovaPushCallback` (see [Callbacks](/docs/push/callbacks)): ```kotlin import ir.abrova.push.sdk.AbrovaPush import ir.abrova.push.sdk.AbrovaPushCallbackAdapter import ir.abrova.push.sdk.AbrovaPushMessage AbrovaPush.setCallback(object : AbrovaPushCallbackAdapter() { override fun onNotificationAction(message: AbrovaPushMessage, actionId: String) { Log.d("AbrovaPush", "Action tapped: $actionId") Log.d("AbrovaPush", "Data: ${message.data}") when (actionId) { "view" -> { val productId = message.data?.get("productId") as? String // Navigate to product } "dismiss" -> { // Handle dismiss } } } }) ``` #### iOS Implement the `AbrovaPushDelegate` method (see [Callbacks](/docs/push/callbacks)): ```swift import AbrovaPushSDK class PushHandler: NSObject, AbrovaPushDelegate { func abrovaPush(_ abrovaPush: AbrovaPush, didReceiveNotificationAction action: NotificationAction, forMessage message: AbrovaPushMessage) { print("Action tapped: \(action.id)") print("Data: \(message.data ?? [:])") switch action.id { case "view": if let productId = message.data?["productId"]?.value as? String { // Navigate to product } case "dismiss": break // Handle dismiss default: break } } } // Keep a strong reference: `delegate` is weak let pushHandler = PushHandler() AbrovaPush.shared.delegate = pushHandler ``` #### React Native ```typescript import AbrovaPush from 'abrova-push-react-native'; AbrovaPush.onNotificationAction((event) => { console.log('Action tapped:', event.actionId); console.log('Title:', event.title); console.log('Data:', event.data); if (event.actionId === 'view') { navigation.navigate('Product', { id: event.data?.productId }); } }); ``` #### Web The SDK's service worker handles `notificationclick`: it opens the clicked action's `action` URL (or the message `deepLink`) and reports the click to the page. Listen for it with `onActionClicked`: ```typescript abrovaPush.onActionClicked((actionId, message) => { console.log('Action tapped:', actionId); if (actionId === 'view') { window.location.href = '/product/' + message.data?.productId; } }); ```

Image Requirements

Large Image (imageUrl)

  • Format: JPEG, PNG, GIF
  • Max size: 1MB
  • Recommended: 1024x512px (2:1 ratio)
  • Must be HTTPS URL

Icon (iconUrl)

  • Format: PNG (transparent)
  • Max size: 256KB
  • Recommended: 192x192px
  • Used by Web notifications

Example: Rich Notification Payload

```json { "userId": "user_123", "title": "Flash Sale! 50% Off", "body": "Your favorite items are on sale for the next 2 hours", "imageUrl": "https://cdn.example.com/sales/flash-sale-banner.jpg", "iconUrl": "https://cdn.example.com/icons/sale-icon.png", "sound": "sale_alert", "badge": 1, "priority": "high", "ttl": 7200, "collapseKey": "flash_sale", "actions": [ { "id": "shop_now", "title": "Shop Now", "action": "OPEN_SALE" }, { "id": "remind_later", "title": "Remind Me", "action": "SET_REMINDER" } ], "data": { "saleId": "sale_flash_001", "expiresAt": "2024-01-15T12:00:00Z", "deepLink": "/sales/flash" } } ``` - رسید تحویل: https://abrova.ir/docs/push/receipts Track notification delivery, opens, clicks, and dismissals in real-time.

Receipt Statuses

APNs confirms only that Apple accepted a notification, never that it arrived, so iOS devices stay at `sent` until you add a Notification Service Extension. It is optional and takes a few minutes — see [iOS Delivery Confirmation](/docs/push/ios). Notifications shown while the app is in the foreground, tapped, or received over the real-time connection are confirmed by the app itself. Android and Web confirm delivery on their own, with no extra setup. Per-device receipts are created for every send: to a user, a device, a list of devices, a topic, a segment, or a broadcast. Each targeted device gets one. The **Delivery** page in the [Abrova Console](https://console.abrova.ir) shows sent, delivered and failed counts per platform, with the most common failure reasons, and a filterable receipt log. ### Transports Which path carried a notification. Every receipt records one. With the FCM bridge on, a push travels over both Android paths. `deliveredVia` then says which copy the device received first, `pn` or `fcm`.

Get Message Stats

Aggregate delivery / open / click rates for a single message. ```bash curl -X GET "https://push-api.abrova.ir/receipts/message/msg_abc123/stats" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json { "total": 1000, "sent": 980, "delivered": 950, "opened": 450, "clicked": 120, "failed": 20, "deliveryRate": 96.94, "openRate": 47.37, "clickRate": 12.63 } ```

Get Individual Receipts

Per-device receipt for a single message — includes timestamps for each lifecycle stage and the action button tapped. ```bash curl -X GET "https://push-api.abrova.ir/receipts/message/msg_abc123" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response Returns an array of receipts, newest first. ```json [ { "id": "rcpt_1", "messageId": "msg_abc123", "deviceId": "dev_xyz", "platform": "ios", "status": "clicked", "sentAt": "2024-01-15T10:00:00Z", "deliveredAt": "2024-01-15T10:00:01Z", "openedAt": "2024-01-15T10:05:00Z", "clickedAt": "2024-01-15T10:05:30Z", "actionId": "shop_now" }, { "id": "rcpt_2", "messageId": "msg_abc123", "deviceId": "dev_abc", "platform": "android", "status": "failed", "sentAt": "2024-01-15T10:00:00Z", "error": "InvalidRegistration" } ] ```

Get Action Stats

See which action buttons users clicked most. ```bash curl -X GET "https://push-api.abrova.ir/receipts/message/msg_abc123/actions" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json [ { "actionId": "shop_now", "count": "85" }, { "actionId": "remind_later", "count": "35" } ] ``` `count` is returned as a string.

Get Device Receipts

Receipt history for a specific device, newest first. `limit` defaults to 50. Each receipt includes its parent `message` object (title, body, ...). ```bash curl -X GET "https://push-api.abrova.ir/receipts/device/dev_xyz?limit=20" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json [ { "id": "rcpt_1", "messageId": "msg_abc123", "deviceId": "dev_xyz", "status": "opened", "sentAt": "2024-01-15T10:00:00Z", "openedAt": "2024-01-15T10:05:00Z", "message": { "id": "msg_abc123", "title": "New message", "body": "..." } }, { "id": "rcpt_2", "messageId": "msg_def456", "deviceId": "dev_xyz", "status": "clicked", "sentAt": "2024-01-14T15:30:00Z", "clickedAt": "2024-01-14T15:32:00Z", "actionId": "view_order", "message": { "id": "msg_def456", "title": "Order update", "body": "..." } } ] ```

Get App-wide Stats

Aggregate stats across the app that owns your API key, for a date range. Without `startDate`/`endDate` it covers the last 30 days. `deliveryRate` here is `delivered / totalRecipients`. ```bash curl -X GET "https://push-api.abrova.ir/receipts/stats?startDate=2024-01-01&endDate=2024-01-31" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" ``` #### Response ```json { "totalMessages": 150, "totalRecipients": 45000, "delivered": 43500, "opened": 18000, "clicked": 4500, "failed": 1500, "deliveryRate": 96.67, "openRate": 41.38, "clickRate": 10.34 } ```

SDK Integration

Receipts are tracked automatically by the SDKs. The only manual hook is on iOS: if the SDK is not your `UNUserNotificationCenter` delegate, call `AbrovaPush.shared.handleRemoteNotification(userInfo:)` from `application(_:didReceiveRemoteNotification:fetchCompletionHandler:)`. Each message is confirmed once per device, and redeliveries are not counted twice. Ensure you have the latest version installed:

Webhooks for Receipts

When a device confirms delivery, a `message.delivered` webhook fires: ```json { "event": "message.delivered", "timestamp": "2024-01-15T10:00:01Z", "webhookId": "wh_123", "data": { "messageId": "msg_abc123", "deviceId": "dev_xyz", "title": "New message" } } ``` #### Available Webhook Events - `message.sent` — Message sent - `message.delivered` — A device confirmed delivery - `message.failed` — Delivery failed - `message.no_recipients` — The target had no registered devices - `device.registered`, `device.unregistered`, `device.updated` — Device lifecycle Opens, clicks and dismissals are recorded on the receipt but do not fire webhooks. - پیام درون‌اپ: https://abrova.ir/docs/push/in-app Display targeted messages to users while they're actively using your app. Perfect for onboarding, feature announcements, promotions, and contextual tips.

Key Features

Multiple Message Types

Modal, Banner, Fullscreen, and Card formats

Smart Triggers

On app open, custom events, or session start, within an optional start/end date window

User Targeting

Target by segments, user IDs, or properties

Built-in Analytics

Track impressions, clicks, and conversions

Message Types

Modal

Centered popup dialog that requires user action. Best for important announcements or confirmations.

Banner

Top or bottom bar that doesn't interrupt the user flow. Good for tips and non-urgent messages.

Fullscreen

Full screen takeover for maximum impact. Ideal for onboarding or major announcements.

Card

Floating card notification that appears in corner. Perfect for contextual tips.

Using the Dashboard

1. In the Abrova Console sidebar, open **AbrovaPush → In-App** 2. Click **Create Message** and choose your message type 3. **Configure Trigger** — Select when the message should appear (on app open, event, etc.) 4. **Add Content** — Set title, body, image, and action buttons 5. **Activate** — Save as draft or activate immediately

Trigger Types

Flutter

**Required:** wrap your app with `AbrovaPushInAppOverlay`. The Flutter plugin forwards every in-app message to Dart; the native SDK does not render its own UI. Without this widget, messages fetch and match but nothing displays. ```dart import 'package:abrova_push/abrova_push.dart'; MaterialApp( builder: (context, child) => AbrovaPushInAppOverlay( onButtonTap: (context, message, button) { if (button.action == InAppButtonAction.deepLink && button.value != null) { Navigator.pushNamed(context, button.value!); } }, child: child ?? const SizedBox.shrink(), ), home: const HomePage(), ); ``` The overlay renders modal, banner, fullscreen, and card messages out of the box, records impressions/clicks/dismisses, respects `Directionality` (RTL), `Theme.of(context).brightness` (dark mode), and safe areas. **Customize styling** with `AbrovaPushInAppTheme`: ```dart AbrovaPushInAppOverlay( theme: const AbrovaPushInAppTheme( modalBorderRadius: 24, modalMaxWidth: 360, bannerBackgroundColor: Color(0xFF1E1E1E), bannerTextColor: Colors.white, animationDuration: Duration(milliseconds: 260), ), child: child!, ); ``` **Fully replace a renderer** with `AbrovaPushInAppBuilders` (any type left null falls back to the default): ```dart AbrovaPushInAppOverlay( builders: AbrovaPushInAppBuilders( banner: (context, message, content, actions) => MyBanner( title: content.title, body: content.body, onTap: () => actions.onButtonTap(content.buttons.first), onClose: actions.onDismiss, ), ), child: child!, ); ``` ```dart // 1. Initialize AbrovaPush (only apiKey required) await AbrovaPush.init(AbrovaPushConfig( apiKey: 'ab_live_your_api_key', )); // 2. Register device (required for in-app messages to work) await AbrovaPush.register(); // 3. Trigger in-app messages based on your trigger type // For "on_app_open" trigger: await AbrovaPush.triggerInAppOnAppOpen(); // For "on_event" trigger (e.g., after purchase): await AbrovaPush.triggerInAppEvent('purchase_completed', properties: {'amount': 99.99}); // For "on_session_start" trigger: await AbrovaPush.triggerInAppOnSessionStart(); // Or manually fetch and show a specific message: final messages = await AbrovaPush.fetchInAppMessages(); if (messages.isNotEmpty) { await AbrovaPush.showInAppMessage(messages.first.id); } // Dismiss current message programmatically: await AbrovaPush.dismissInAppMessage(); ```

Android

The SDK shows messages with its built-in UI when the current Activity is set with `AbrovaPush.setCurrentActivity(this)` (call it in `onResume()`) and **no** `InAppMessageCallback` is set. Setting a callback hands rendering to you: the SDK then records the impression and calls `onMessageReady`, but shows nothing (this is how the Flutter and React Native wrappers take over). `onButtonClicked` and `onMessageDismissed` are called only by the built-in UI, which does not run while a callback is set. ```kotlin import ir.abrova.push.sdk.AbrovaPush import ir.abrova.push.sdk.inapp.* // Built-in UI: tell the SDK which Activity to draw on (in onResume) AbrovaPush.setCurrentActivity(this) // Optional, only if you render messages yourself (disables the built-in UI) AbrovaPush.setInAppMessageCallback(object : InAppMessageCallback { override fun onMessageReady(message: InAppMessage) { // Render the message with your own UI Log.d("InApp", "Message ready: ${message.id}") } override fun onButtonClicked(message: InAppMessage, button: InAppButton) { // Handle button click when (button.action) { InAppButtonAction.DEEP_LINK -> { // Navigate to deep link val intent = Intent(Intent.ACTION_VIEW, Uri.parse(button.value)) startActivity(intent) } InAppButtonAction.URL -> { // Open URL in browser val intent = Intent(Intent.ACTION_VIEW, Uri.parse(button.value)) startActivity(intent) } InAppButtonAction.CUSTOM -> { // Handle custom action handleCustomAction(button.value) } else -> {} // Dismiss handled automatically } } override fun onMessageDismissed(message: InAppMessage) { Log.d("InApp", "Message dismissed: ${message.id}") } override fun onError(error: String) { Log.e("InApp", "In-app error: $error") } }) // Trigger in-app messages AbrovaPush.triggerInAppOnAppOpen() AbrovaPush.triggerInAppEvent("viewed_product") AbrovaPush.triggerInAppOnSessionStart() // Or manually fetch and show messages AbrovaPush.fetchInAppMessages { messages -> if (messages.isNotEmpty()) { AbrovaPush.showInAppMessage(messages.first().id) } } // Dismiss current message AbrovaPush.dismissInAppMessage() ```

iOS

As on Android, the built-in UI needs a view controller to present from (`AbrovaPush.shared.setCurrentViewController(_:)`) and runs only while **no** `InAppMessageCallback` is set. With a callback set, the SDK records the impression and calls `inAppMessageReady` but shows nothing; `inAppMessageButtonClicked` and `inAppMessageDismissed` come only from the built-in UI. ```swift import AbrovaPushSDK // Built-in UI: give the SDK a view controller to present from AbrovaPush.shared.setCurrentViewController(viewController) // Optional, only if you render messages yourself (disables the built-in UI) class MyInAppHandler: InAppMessageCallback { func inAppMessageReady(_ message: InAppMessage) { print("Message ready: \(message.id)") } func inAppMessageButtonClicked(_ message: InAppMessage, button: InAppButton) { switch button.action { case .deepLink: if let urlString = button.value, let url = URL(string: urlString) { UIApplication.shared.open(url) } case .url: if let urlString = button.value, let url = URL(string: urlString) { UIApplication.shared.open(url) } case .custom: handleCustomAction(button.value) default: break // Dismiss handled automatically } } func inAppMessageDismissed(_ message: InAppMessage) { print("Message dismissed: \(message.id)") } func inAppMessageError(_ error: String) { print("In-app error: \(error)") } } // Register the callback. It is held weakly, so keep a strong reference. let inAppHandler = MyInAppHandler() AbrovaPush.shared.setInAppMessageCallback(inAppHandler) // Trigger in-app messages AbrovaPush.shared.triggerInAppOnAppOpen() AbrovaPush.shared.triggerInAppEvent("viewed_product") AbrovaPush.shared.triggerInAppOnSessionStart() // Or manually fetch and show messages AbrovaPush.shared.fetchInAppMessages { messages in if let first = messages.first { AbrovaPush.shared.showInAppMessage(first.id) } } // Dismiss current message AbrovaPush.shared.dismissInAppMessage() ```

React Native

```typescript import AbrovaPush, { InAppButtonAction } from 'abrova-push-react-native'; // The React Native wrapper has no built-in in-app UI yet: when a trigger matches, // the native SDK records the impression and emits onInAppMessageReady, and your // app renders the message. onInAppButtonClick / onInAppMessageDismissed come only // from the native SDKs' built-in UI, which the wrapper turns off, so they do not fire. // To fetch messages and emit one manually: const messages = await AbrovaPush.fetchInAppMessages(); if (messages.length > 0) { await AbrovaPush.showInAppMessage(messages[0].id); } // Render the message yourself when it is ready AbrovaPush.onInAppMessageReady((message) => { console.log('Message ready:', message.id); // e.g. show your own modal from message.content; for its buttons: // if (button.action === InAppButtonAction.DEEP_LINK && button.value) { // navigation.navigate(button.value); // } }); // Trigger messages await AbrovaPush.triggerInAppOnAppOpen(); await AbrovaPush.triggerInAppEvent('viewed_product'); await AbrovaPush.triggerInAppOnSessionStart(); ```

Web

Requires `@abrova/push-web` 0.1.7 or newer, and a registered device. In-app messages are on by default and render in a built-in shadow-DOM UI. ```typescript import AbrovaPush from '@abrova/push-web'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', inApp: { // enabled: true, // default // display: 'auto', // 'manual' = render nothing, only fire onMessageReady // autoTrigger: false, // true = fire on_session_start + on_app_open on page load // bannerPosition: 'top', }, }); await abrovaPush.register({ userId: 'user_123' }); // Trigger messages await abrovaPush.inApp.triggerOnAppOpen(); await abrovaPush.inApp.triggerEvent('viewed_product', { productId: 'prod_123' }); await abrovaPush.inApp.triggerSessionStart(); // Or fetch and show one manually const messages = await abrovaPush.inApp.fetchMessages(); if (messages.length > 0) { await abrovaPush.inApp.showMessage(messages[0].id); } // Listen to interactions (each returns an unsubscribe function) abrovaPush.inApp.onMessageReady((message) => console.log('Ready:', message.id)); abrovaPush.inApp.onButtonClicked((message, button) => { // url / deep_link buttons are opened in a new tab by the SDK; // custom buttons are left to your app if (button.action === 'custom') { handleCustomAction(button.value); } }); abrovaPush.inApp.onDismissed((message) => console.log('Dismissed:', message.id)); // Close the message on screen abrovaPush.inApp.dismissCurrentMessage(); ```

Configuration Options

**Audience targeting is mutually exclusive.** A message reaches its audience one of three ways: 1. **Everyone** — set neither `segmentId` nor `targetUserIds` (default). 2. **A segment** — set `segmentId` to a saved segment UUID. Segment membership is checked on every fetch and matches the Console preview. 3. **Explicit user list** — set `targetUserIds` to an array of user IDs (as set via SDK `setUserId()`). Setting both `segmentId` and `targetUserIds` is rejected by the API — the dashboard's Audience picker enforces the same rule visually.

Button Actions

Configure what happens when users tap buttons on your in-app messages. #### Handling Custom Actions In Flutter, handle them in the overlay's `onButtonTap` (the overlay renders the message, so the native `onInAppButtonClick` callback does not fire for it): ```dart AbrovaPushInAppOverlay( onButtonTap: (context, message, button) { if (button.action == InAppButtonAction.custom) { switch (button.value) { case 'share_promo': Share.share('Use code SUMMER20 for 20% off!'); break; case 'rate_app': StoreRedirect.redirect( androidAppId: 'com.yourapp', iOSAppId: '123456789' ); break; case 'start_subscription': Navigator.pushNamed(context, '/subscription'); break; case 'play_video': showVideoPlayer(videoUrl: 'https://...'); break; } } }, child: child!, ); ```

Common Use Cases

Onboarding

Welcome new users and guide them through your app.

  • • Type: Fullscreen
  • • Trigger: on_app_open
  • • minSessionCount: 0 (no minimum, so new users qualify)
  • • maxImpressions: 1

Feature Announcement

Introduce new features to existing users.

  • • Type: Modal
  • • Trigger: on_app_open
  • • minSessionCount: 5 (engaged users)
  • • delaySeconds: 3

Contextual Tip

Show tips when users visit specific screens.

  • • Type: Banner or Card
  • • Trigger: on_event
  • • Event: viewed_settings
  • • maxImpressions: 2

Time-Limited Promotion

Run limited-time offers with scheduling.

  • • Type: Modal
  • • Trigger: on_app_open
  • • startDate/endDate: Campaign dates
  • • maxImpressions: 3

Post-Purchase Upsell

Cross-sell after a successful purchase.

  • • Type: Card
  • • Trigger: on_event
  • • Event: purchase_completed
  • • delaySeconds: 2

VIP User Message

Target specific high-value users.

  • • Type: Modal
  • • Trigger: on_app_open
  • • targetUserIds: [user1, user2]
  • • priority: 10 (show first)

Best Practices

Do

  • • Keep messages concise and actionable
  • • Use appropriate message type for context
  • • Set frequency caps to avoid fatigue
  • • A/B test different message variations
  • • Target messages to relevant user segments
  • • Use scheduling for time-sensitive campaigns
  • • Add delay to avoid interrupting app launch
  • • Use minSessionCount to target engaged users

Don't

  • • Show too many messages per session
  • • Use fullscreen for minor updates
  • • Interrupt critical user flows
  • • Forget to add a dismiss option
  • • Use aggressive or misleading CTAs
  • • Set maxImpressions too high (causes fatigue)
  • • Show messages immediately on app open (use delay)
  • • Target new users with advanced feature tips

Analytics & Metrics

Track the performance of your in-app messages with built-in analytics. - صندوق: https://abrova.ir/docs/push/inbox A persistent message center for your users. Unlike push notifications, inbox messages are stored and can be retrieved later — users never miss important updates.

Why Use Message Inbox?

Persistent Storage

Messages are stored and accessible anytime, unlike ephemeral push notifications

Read Tracking

Know when users read your messages with built-in analytics

Category Filtering

Organize messages by category (promotions, updates, alerts)

User Control

Users can archive, delete, or mark messages as read

Use Cases

Announcements

Product updates, new features, company news that users should be able to reference later.

Promotions

Special offers, discounts, and deals with expiration dates that users can redeem at their convenience.

Transactional

Order updates, account alerts, and important notifications that need to be preserved.

Using the Dashboard

In Abrova Console, open **AbrovaPush → Inbox**. It provides four ways to send messages:
Send to User

Send a message to a specific user by their ID

Send to Device

Send a message to a specific device by its device ID

Send to Multiple

Send to multiple users by providing a comma-separated list of user IDs

Broadcast to All

Send to all users of your app at once

Send to User

#### Node.js SDK ```typescript import { AbrovaPush } from '@abrova/push-node'; const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key', serverSecret: 'ab_srv_your_server_secret', }); await abrovaPush.inbox.sendToUser({ userId: 'user_123', content: { title: 'New Promotion!', body: 'Get 20% off your next purchase', imageUrl: 'https://example.com/promo.jpg', deepLink: 'myapp://promotions/123', }, category: 'promotions', expiresAt: '2024-02-01T00:00:00Z', }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/inbox/api/send-to-user" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_123", "content": { "title": "New Promotion!", "body": "Get 20% off your next purchase", "imageUrl": "https://example.com/promo.jpg", "deepLink": "myapp://promotions/123" }, "category": "promotions", "expiresAt": "2024-02-01T00:00:00Z" }' ```

Send to Multiple Users

#### Node.js SDK ```typescript await abrovaPush.inbox.sendToUsers({ userIds: ['user_123', 'user_456', 'user_789'], content: { title: 'Flash Sale!', body: 'Limited time offer for VIP members', }, category: 'promotions', }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/inbox/api/send-to-users" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "userIds": ["user_123", "user_456", "user_789"], "content": { "title": "Flash Sale!", "body": "Limited time offer for VIP members" }, "category": "promotions" }' ```

Broadcast to All Users

#### Node.js SDK ```typescript await abrovaPush.inbox.broadcast({ content: { title: 'App Update Available', body: 'We have released new features! Check them out.', }, category: 'announcements', }); ``` #### REST API ```bash curl -X POST "https://push-api.abrova.ir/inbox/api/broadcast" \ -H "x-api-key: ab_live_your_api_key" \ -H "x-server-secret: ab_srv_your_server_secret" \ -H "Content-Type: application/json" \ -d '{ "content": { "title": "App Update Available", "body": "We have released new features! Check them out." }, "category": "announcements" }' ```

Message Fields