@symbo.ls/sdk 2.32.10 → 2.32.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +141 -0
  2. package/dist/cjs/config/environment.js +18 -7
  3. package/dist/cjs/index.js +21 -11
  4. package/dist/cjs/services/BaseService.js +46 -0
  5. package/dist/cjs/services/DnsService.js +6 -5
  6. package/dist/cjs/services/TrackingService.js +668 -0
  7. package/dist/cjs/services/index.js +5 -5
  8. package/dist/cjs/utils/changePreprocessor.js +8 -1
  9. package/dist/cjs/utils/services.js +27 -3
  10. package/dist/esm/config/environment.js +18 -7
  11. package/dist/esm/index.js +20737 -5911
  12. package/dist/esm/services/AdminService.js +64 -7
  13. package/dist/esm/services/AuthService.js +64 -7
  14. package/dist/esm/services/BaseService.js +64 -7
  15. package/dist/esm/services/BranchService.js +64 -7
  16. package/dist/esm/services/CollabService.js +72 -8
  17. package/dist/esm/services/DnsService.js +70 -12
  18. package/dist/esm/services/FileService.js +64 -7
  19. package/dist/esm/services/PaymentService.js +64 -7
  20. package/dist/esm/services/PlanService.js +64 -7
  21. package/dist/esm/services/ProjectService.js +72 -8
  22. package/dist/esm/services/PullRequestService.js +64 -7
  23. package/dist/esm/services/ScreenshotService.js +64 -7
  24. package/dist/esm/services/SubscriptionService.js +64 -7
  25. package/dist/esm/services/TrackingService.js +18328 -0
  26. package/dist/esm/services/index.js +20673 -5881
  27. package/dist/esm/utils/CollabClient.js +18 -7
  28. package/dist/esm/utils/changePreprocessor.js +8 -1
  29. package/dist/esm/utils/services.js +27 -3
  30. package/dist/node/config/environment.js +18 -7
  31. package/dist/node/index.js +25 -15
  32. package/dist/node/services/BaseService.js +46 -0
  33. package/dist/node/services/DnsService.js +6 -5
  34. package/dist/node/services/TrackingService.js +639 -0
  35. package/dist/node/services/index.js +5 -5
  36. package/dist/node/utils/changePreprocessor.js +8 -1
  37. package/dist/node/utils/services.js +27 -3
  38. package/package.json +8 -6
  39. package/src/config/environment.js +19 -11
  40. package/src/index.js +24 -14
  41. package/src/services/BaseService.js +43 -0
  42. package/src/services/DnsService.js +5 -5
  43. package/src/services/TrackingService.js +861 -0
  44. package/src/services/index.js +6 -5
  45. package/src/utils/changePreprocessor.js +25 -1
  46. package/src/utils/services.js +28 -4
  47. package/dist/cjs/services/CoreService.js +0 -2818
  48. package/dist/esm/services/CoreService.js +0 -3513
  49. package/dist/node/services/CoreService.js +0 -2789
  50. package/src/services/CoreService.js +0 -3208
package/README.md CHANGED
@@ -377,6 +377,147 @@ const ai = sdk.getService('ai')
377
377
  const response = await ai.prompt(query, options)
378
378
  ```
379
379
 
380
+ ### Tracking Service (Grafana Faro)
381
+ ```javascript
382
+ // 1) Initialize SDK with tracking config (early in app startup)
383
+ const sdk = new SDK({
384
+ useNewServices: true,
385
+ apiUrl: 'https://api.symbols.app',
386
+ // Tracking configuration mirrors TrackingService options
387
+ tracking: {
388
+ url: 'https://<your-faro-receiver-url>', // FO ingest/collector URL
389
+ appName: 'Symbols Platform',
390
+ environment: 'development', // 'production' | 'staging' | 'testing' | 'development'
391
+ appVersion: '1.0.0',
392
+ sessionTracking: true,
393
+ enableTracing: true, // adds browser tracing when available
394
+ globalAttributes: { region: 'us-east-1' }
395
+ }
396
+ })
397
+ await sdk.initialize()
398
+
399
+ // 2) Get the tracking service
400
+ const tracking = sdk.getService('tracking')
401
+
402
+ // 3) Send signals
403
+ tracking.trackEvent('purchase', { amount: 42, currency: 'USD' })
404
+ tracking.trackMeasurement('cart_value', { value: 42 })
405
+ tracking.logError('checkout failed', { step: 'payment' })
406
+ tracking.trackView('Checkout', { stage: 'payment' })
407
+ tracking.setUser({ id: 'u_123', email: 'user@example.com' })
408
+ ```
409
+
410
+ #### Configuration
411
+ Provide these under `tracking` when creating the `SDK` (or later via `tracking.configureTracking()`):
412
+
413
+ - `url` string: Frontend Observability/Faro ingestion URL. If omitted and no custom transports are provided, tracking is disabled.
414
+ - `appName` string: Logical application name used in Grafana dashboards.
415
+ - `appVersion` string: App version shown in Grafana.
416
+ - `environment` string: One of your environments; default resolves from runtime (`production`, `staging`, `testing`, `development`).
417
+ - `sessionTracking` boolean: Enable Faro session tracking. Default: `true`.
418
+ - `enableTracing` boolean: Enable web tracing and send to Tempo (if collector configured). Default: `true`.
419
+ - `globalAttributes` object: Key/values merged into every signal.
420
+ - `user` object: Initial user attributes.
421
+ - `maxQueueSize` number: Max queued calls before client setup. Default: `100`.
422
+ - `isolate` boolean: Create an isolated Faro instance.
423
+ - `transports` array | `transport` any: Custom transports (advanced).
424
+ - `instrumentations` array | `instrumentationsFactory(runtime) => Promise<array>` | `webInstrumentationOptions` object: Control Faro web instrumentations.
425
+
426
+ Note:
427
+ - Tracking is automatically disabled in non‑browser environments.
428
+ - Calls are queued until the Faro client is ready. For specific calls, pass `{ queue: false }` to skip queuing.
429
+
430
+ #### Method reference
431
+ The following methods are available via `sdk.getService('tracking')` and map to `utils/services.js`:
432
+
433
+ - `configureTracking(trackingOptions)` / `configure(trackingOptions)`: Merge/override runtime tracking options (supports all config keys above).
434
+ - `trackEvent(name, attributes?, options?)`
435
+ - `name` string (required)
436
+ - `attributes` object merged with global attributes
437
+ - `options` object:
438
+ - `domain` string | null
439
+ - `queue` boolean (whether to queue if client not ready)
440
+ - Additional transport options are forwarded to Faro
441
+ - Example:
442
+ ```javascript
443
+ tracking.trackEvent('signup_attempt', { method: 'email' }, { domain: 'auth' })
444
+ ```
445
+ - `trackError(error, options?)` / `captureException(error, options?)`
446
+ - `error` Error | string
447
+ - `options` can be:
448
+ - object with Faro error options (`context`, `type`, `stackFrames`, `skipDedupe`, `timestampOverwriteMs`, etc.)
449
+ - or a plain context object (shorthand)
450
+ - `queue` boolean supported
451
+ - Example:
452
+ ```javascript
453
+ tracking.trackError(new Error('Login failed'), { context: { screen: 'Login' } })
454
+ ```
455
+ - `logMessage(message, level='info', context?)`
456
+ - Convenience wrappers: `logDebug`, `logInfo`, `logWarning`/`logWarn`, `logErrorMessage`/`logError`
457
+ - `message` string | string[]
458
+ - `context` object merged with global attributes
459
+ - Example:
460
+ ```javascript
461
+ tracking.logWarning('Slow response', { route: '/checkout', ttfbMs: 900 })
462
+ ```
463
+ - `addBreadcrumb(message, attributes?)`
464
+ - Adds a low‑cost breadcrumb via `trackEvent('breadcrumb', ...)`
465
+ - Example:
466
+ ```javascript
467
+ tracking.addBreadcrumb('Open modal', { id: 'planLimits' })
468
+ ```
469
+ - `trackMeasurement(type, values, options?)`
470
+ - `type` string (required)
471
+ - `values` object | number. If number, it becomes `{ value: <number> }`.
472
+ - `options`:
473
+ - `attributes` object (merged into payload.attributes)
474
+ - `context` object (transport context)
475
+ - `queue` boolean
476
+ - Any additional transport options
477
+ - Example:
478
+ ```javascript
479
+ tracking.trackMeasurement('cart_value', 42, { context: { currency: 'USD' } })
480
+ ```
481
+ - `trackView(name, attributes?)`
482
+ - Sets the current view/page in Faro
483
+ - Example:
484
+ ```javascript
485
+ tracking.trackView('Dashboard', { section: 'Analytics' })
486
+ ```
487
+ - `setUser(user, options?)` / `clearUser()`
488
+ - `user` object with arbitrary attributes; supports `{ queue: boolean }`
489
+ - Example:
490
+ ```javascript
491
+ tracking.setUser({ id: 'u_123', role: 'admin' })
492
+ ```
493
+ - `setSession(session, options?)` / `clearSession()`
494
+ - Attach custom session data; supports `{ queue: boolean, ...sessionOptions }`
495
+ - `setGlobalAttributes(attributes)` / `setGlobalAttribute(key, value)` / `removeGlobalAttribute(key)`
496
+ - Manage the global attributes merged into every signal
497
+ - `flushQueue()`
498
+ - Immediately runs all queued calls (no‑op if client not ready)
499
+ - `getClient()`
500
+ - Returns the underlying Faro client (or `null` if not ready)
501
+ - `isEnabled()` / `isInitialized()`
502
+ - Status helpers
503
+
504
+ #### Example: auth error tracking from services
505
+ The SDK’s services automatically send errors to tracking:
506
+ ```javascript
507
+ try {
508
+ await auth.login(email, password)
509
+ } catch (error) {
510
+ // BaseService forwards details to tracking.trackError(...)
511
+ }
512
+ ```
513
+
514
+ #### Visualizing in Grafana
515
+ - Use the Frontend Observability (Faro) data source and pick:
516
+ - Service = your `appName`
517
+ - Environment = your `environment`
518
+ - Panels for page loads and Web Vitals require web instrumentations and real page traffic.
519
+ - If self‑hosting with a Faro collector → Loki/Tempo, ensure the FO app is installed and the dashboard uses the FO data source; otherwise create custom panels with LogQL over Loki.
520
+
380
521
  ## Error Handling
381
522
  ```javascript
382
523
  try {
@@ -30,7 +30,9 @@ const CONFIG = {
30
30
  // Feature toggles that apply across all environments by default
31
31
  features: {
32
32
  newUserOnboarding: true,
33
- betaFeatures: false
33
+ betaFeatures: false,
34
+ // Tracking is enabled by default unless overridden per environment
35
+ trackingEnabled: true
34
36
  }
35
37
  },
36
38
  // Environment-specific configurations
@@ -48,13 +50,17 @@ const CONFIG = {
48
50
  // For based api
49
51
  githubClientId: "Ov23liAFrsR0StbAO6PO",
50
52
  // For github api
51
- grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/aef64330db80bdfeaac084317bf72f99",
53
+ grafanaUrl: "",
52
54
  // For grafana tracing
53
- grafanaAppName: "Localhost Symbols",
55
+ grafanaAppName: "Symbols Localhost",
54
56
  // Environment-specific feature toggles (override common)
55
57
  features: {
56
- betaFeatures: true
58
+ // Disable tracking by default on localhost/dev machines
59
+ trackingEnabled: false,
57
60
  // Enable beta features in local dev
61
+ betaFeatures: true,
62
+ // Preserve common defaults explicitly for local
63
+ newUserOnboarding: true
58
64
  },
59
65
  typesenseCollectionName: "docs",
60
66
  typesenseApiKey: "vZya3L2zpq8L6iI5WWMUZJZABvT63VDb",
@@ -82,7 +88,7 @@ const CONFIG = {
82
88
  basedProject: "platform-v2-sm",
83
89
  basedOrg: "symbols",
84
90
  githubClientId: "Ov23liHxyWFBxS8f1gnF",
85
- grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/7a3ba473cee2025c68513667024316b8",
91
+ grafanaUrl: "",
86
92
  // For grafana tracing
87
93
  grafanaAppName: "Symbols Test",
88
94
  typesenseCollectionName: "docs",
@@ -95,7 +101,7 @@ const CONFIG = {
95
101
  socketUrl: "https://upcoming.api.symbols.app",
96
102
  apiUrl: "https://upcoming.api.symbols.app",
97
103
  githubClientId: "Ov23liWF7NvdZ056RV5J",
98
- grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/7a3ba473cee2025c68513667024316b8",
104
+ grafanaUrl: "",
99
105
  // For grafana tracing
100
106
  grafanaAppName: "Symbols Upcoming",
101
107
  typesenseCollectionName: "docs",
@@ -111,7 +117,7 @@ const CONFIG = {
111
117
  basedProject: "platform-v2-sm",
112
118
  basedOrg: "symbols",
113
119
  githubClientId: "Ov23ligwZDQVD0VfuWNa",
114
- grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/7a3ba473cee2025c68513667024316b8",
120
+ grafanaUrl: "",
115
121
  // For grafana tracing
116
122
  grafanaAppName: "Symbols Staging",
117
123
  typesenseCollectionName: "docs",
@@ -151,6 +157,11 @@ const getConfig = () => {
151
157
  const envConfig = { ...CONFIG.common, ...CONFIG[env] };
152
158
  const finalConfig = {
153
159
  ...envConfig,
160
+ // Deep-merge feature flags so env-specific overrides don't drop common defaults
161
+ features: {
162
+ ...CONFIG.common.features || {},
163
+ ...CONFIG[env] && CONFIG[env].features || {}
164
+ },
154
165
  socketUrl: process.env.SYMBOLS_APP_SOCKET_URL || envConfig.socketUrl,
155
166
  apiUrl: process.env.SYMBOLS_APP_API_URL || envConfig.apiUrl,
156
167
  basedEnv: process.env.SYMBOLS_APP_BASED_ENV || envConfig.basedEnv,
package/dist/cjs/index.js CHANGED
@@ -32,7 +32,6 @@ __export(index_exports, {
32
32
  createAuthService: () => import_services3.createAuthService,
33
33
  createBranchService: () => import_services3.createBranchService,
34
34
  createCollabService: () => import_services3.createCollabService,
35
- createCoreService: () => import_services3.createCoreService,
36
35
  createDnsService: () => import_services3.createDnsService,
37
36
  createFileService: () => import_services3.createFileService,
38
37
  createPaymentService: () => import_services3.createPaymentService,
@@ -40,6 +39,7 @@ __export(index_exports, {
40
39
  createProjectService: () => import_services3.createProjectService,
41
40
  createPullRequestService: () => import_services3.createPullRequestService,
42
41
  createSubscriptionService: () => import_services3.createSubscriptionService,
42
+ createTrackingService: () => import_services3.createTrackingService,
43
43
  default: () => index_default,
44
44
  environment: () => import_environment2.default
45
45
  });
@@ -72,13 +72,6 @@ class SDK {
72
72
  options: this._options
73
73
  })
74
74
  ),
75
- this._initService(
76
- "core",
77
- (0, import_services.createCoreService)({
78
- context: this._context,
79
- options: this._options
80
- })
81
- ),
82
75
  this._initService(
83
76
  "collab",
84
77
  (0, import_services.createCollabService)({
@@ -156,6 +149,13 @@ class SDK {
156
149
  context: this._context,
157
150
  options: this._options
158
151
  })
152
+ ),
153
+ this._initService(
154
+ "tracking",
155
+ (0, import_services.createTrackingService)({
156
+ context: this._context,
157
+ options: this._options
158
+ })
159
159
  )
160
160
  ]);
161
161
  return this;
@@ -180,9 +180,19 @@ class SDK {
180
180
  socketUrl: import_environment.default.socketUrl,
181
181
  timeout: 3e4,
182
182
  retryAttempts: 3,
183
- debug: false
183
+ debug: false,
184
+ tracking: {
185
+ enabled: import_environment.default.features.trackingEnabled
186
+ }
187
+ };
188
+ return {
189
+ ...defaults,
190
+ ...options,
191
+ tracking: {
192
+ ...defaults.tracking,
193
+ ...options.tracking || {}
194
+ }
184
195
  };
185
- return { ...defaults, ...options };
186
196
  }
187
197
  // Get service instance
188
198
  getService(name) {
@@ -193,7 +203,7 @@ class SDK {
193
203
  }
194
204
  // Update context
195
205
  updateContext(newContext) {
196
- const { authToken, ...sanitized } = newContext || {};
206
+ const { ...sanitized } = newContext || {};
197
207
  this._context = {
198
208
  ...this._context,
199
209
  ...sanitized
@@ -88,6 +88,39 @@ class BaseService {
88
88
  this._ready = false;
89
89
  this._error = error;
90
90
  }
91
+ _getTrackingService() {
92
+ var _a;
93
+ const services = (_a = this._context) == null ? void 0 : _a.services;
94
+ const tracking = services == null ? void 0 : services.tracking;
95
+ if (!tracking || typeof tracking.trackError !== "function") {
96
+ return null;
97
+ }
98
+ return tracking;
99
+ }
100
+ _shouldTrackErrors() {
101
+ var _a;
102
+ const name = (_a = this == null ? void 0 : this.constructor) == null ? void 0 : _a.name;
103
+ return name !== "TrackingService";
104
+ }
105
+ _trackServiceError(error, details = {}) {
106
+ var _a;
107
+ if (!this._shouldTrackErrors()) {
108
+ return;
109
+ }
110
+ try {
111
+ const tracking = this._getTrackingService();
112
+ if (!tracking) {
113
+ return;
114
+ }
115
+ const context = {
116
+ service: ((_a = this == null ? void 0 : this.constructor) == null ? void 0 : _a.name) || "UnknownService",
117
+ apiUrl: this._apiUrl || null,
118
+ ...details
119
+ };
120
+ tracking.trackError(error instanceof Error ? error : new Error(String(error)), context);
121
+ } catch {
122
+ }
123
+ }
91
124
  _requireAuth() {
92
125
  if (!this._context.authToken) {
93
126
  throw new Error("Authentication required");
@@ -137,10 +170,23 @@ class BaseService {
137
170
  error = await response.json();
138
171
  } catch {
139
172
  }
173
+ this._trackServiceError(
174
+ new Error(error.message || error.error || `HTTP ${response.status}: ${response.statusText}`),
175
+ {
176
+ endpoint,
177
+ methodName: options.methodName,
178
+ status: response.status,
179
+ statusText: response.statusText
180
+ }
181
+ );
140
182
  throw new Error(error.message || error.error || "Request failed", { cause: error });
141
183
  }
142
184
  return response.status === 204 ? null : response.json();
143
185
  } catch (error) {
186
+ this._trackServiceError(error, {
187
+ endpoint,
188
+ methodName: options.methodName
189
+ });
144
190
  throw new Error(`Request failed: ${error.message}`, { cause: error });
145
191
  }
146
192
  }
@@ -75,7 +75,7 @@ class DnsService extends import_BaseService.BaseService {
75
75
  }
76
76
  throw new Error(response.message);
77
77
  } catch (error) {
78
- throw new Error(`Failed to get custom host: ${error.message}`);
78
+ throw new Error(`Failed to get custom host: ${error.message}`, { cause: error });
79
79
  }
80
80
  }
81
81
  async removeDnsRecord(domain) {
@@ -143,7 +143,8 @@ class DnsService extends import_BaseService.BaseService {
143
143
  throw new Error(response.message);
144
144
  } catch (error) {
145
145
  throw new Error(
146
- `Failed to update project custom domains: ${error.message}`
146
+ `Failed to update project custom domains: ${error.message}`,
147
+ { cause: error }
147
148
  );
148
149
  }
149
150
  }
@@ -301,7 +302,7 @@ class DnsService extends import_BaseService.BaseService {
301
302
  needsVerification: false
302
303
  };
303
304
  } catch (error) {
304
- throw new Error(`Failed to verify domain ownership: ${error.message}`);
305
+ throw new Error(`Failed to verify domain ownership: ${error.message}`, { cause: error });
305
306
  }
306
307
  }
307
308
  /**
@@ -322,7 +323,7 @@ class DnsService extends import_BaseService.BaseService {
322
323
  }
323
324
  throw new Error(response.message);
324
325
  } catch (error) {
325
- throw new Error(`Failed to get project domains: ${error.message}`);
326
+ throw new Error(`Failed to get project domains: ${error.message}`, { cause: error });
326
327
  }
327
328
  }
328
329
  /**
@@ -346,7 +347,7 @@ class DnsService extends import_BaseService.BaseService {
346
347
  }
347
348
  throw new Error(response.message);
348
349
  } catch (error) {
349
- throw new Error(`Failed to remove project custom domain: ${error.message}`);
350
+ throw new Error(`Failed to remove project custom domain: ${error.message}`, { cause: error });
350
351
  }
351
352
  }
352
353
  /**