@symbo.ls/sdk 2.32.11 → 2.32.13

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 +38 -12
  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 +661 -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 +20747 -5912
  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 +18321 -0
  26. package/dist/esm/services/index.js +20667 -5882
  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 +42 -16
  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 +632 -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 +44 -14
  41. package/src/services/BaseService.js +43 -0
  42. package/src/services/DnsService.js +5 -5
  43. package/src/services/TrackingService.js +853 -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,8 +39,10 @@ __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
- environment: () => import_environment2.default
44
+ environment: () => import_environment2.default,
45
+ isLocalhost: () => isLocalhost
45
46
  });
46
47
  module.exports = __toCommonJS(index_exports);
47
48
  var import_services = require("./services/index.js");
@@ -50,6 +51,14 @@ var import_environment = __toESM(require("./config/environment.js"), 1);
50
51
  var import_rootEventBus = require("./state/rootEventBus.js");
51
52
  var import_services3 = require("./services/index.js");
52
53
  var import_environment2 = __toESM(require("./config/environment.js"), 1);
54
+ const isBrowserEnvironment = () => typeof window !== "undefined";
55
+ const isLocalhost = () => {
56
+ if (!isBrowserEnvironment()) {
57
+ return false;
58
+ }
59
+ const host = window.location && window.location.hostname;
60
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "" || !host;
61
+ };
53
62
  class SDK {
54
63
  constructor(options = {}) {
55
64
  this._services = /* @__PURE__ */ new Map();
@@ -72,13 +81,6 @@ class SDK {
72
81
  options: this._options
73
82
  })
74
83
  ),
75
- this._initService(
76
- "core",
77
- (0, import_services.createCoreService)({
78
- context: this._context,
79
- options: this._options
80
- })
81
- ),
82
84
  this._initService(
83
85
  "collab",
84
86
  (0, import_services.createCollabService)({
@@ -156,6 +158,13 @@ class SDK {
156
158
  context: this._context,
157
159
  options: this._options
158
160
  })
161
+ ),
162
+ this._initService(
163
+ "tracking",
164
+ (0, import_services.createTrackingService)({
165
+ context: this._context,
166
+ options: this._options
167
+ })
159
168
  )
160
169
  ]);
161
170
  return this;
@@ -173,6 +182,8 @@ class SDK {
173
182
  this._services.set(name, service);
174
183
  }
175
184
  _validateOptions(options) {
185
+ const onLocalhost = isLocalhost();
186
+ const hasGrafanaUrl = Boolean(import_environment.default.grafanaUrl);
176
187
  const defaults = {
177
188
  useNewServices: true,
178
189
  // Use new service implementations by default
@@ -180,9 +191,24 @@ class SDK {
180
191
  socketUrl: import_environment.default.socketUrl,
181
192
  timeout: 3e4,
182
193
  retryAttempts: 3,
183
- debug: false
194
+ debug: false,
195
+ tracking: {
196
+ // Force-disabled on localhost or when no Grafana URL is configured
197
+ enabled: onLocalhost ? false : hasGrafanaUrl ? import_environment.default.features.trackingEnabled : false
198
+ }
199
+ };
200
+ const merged = {
201
+ ...defaults,
202
+ ...options,
203
+ tracking: {
204
+ ...defaults.tracking,
205
+ ...options.tracking || {}
206
+ }
184
207
  };
185
- return { ...defaults, ...options };
208
+ if (onLocalhost || !hasGrafanaUrl) {
209
+ merged.tracking.enabled = false;
210
+ }
211
+ return merged;
186
212
  }
187
213
  // Get service instance
188
214
  getService(name) {
@@ -193,7 +219,7 @@ class SDK {
193
219
  }
194
220
  // Update context
195
221
  updateContext(newContext) {
196
- const { authToken, ...sanitized } = newContext || {};
222
+ const { ...sanitized } = newContext || {};
197
223
  this._context = {
198
224
  ...this._context,
199
225
  ...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
  /**