swetrix 3.7.1 → 4.0.0

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.
package/src/Lib.ts CHANGED
@@ -32,6 +32,12 @@ export interface LibOptions {
32
32
 
33
33
  /** Set a custom URL of the API server (for selfhosted variants of Swetrix). */
34
34
  apiURL?: string
35
+
36
+ /**
37
+ * Optional profile ID for long-term user tracking.
38
+ * If set, it will be used for all pageviews and events unless overridden per-call.
39
+ */
40
+ profileId?: string
35
41
  }
36
42
 
37
43
  export interface TrackEventOptions {
@@ -45,6 +51,9 @@ export interface TrackEventOptions {
45
51
  meta?: {
46
52
  [key: string]: string | number | boolean | null | undefined
47
53
  }
54
+
55
+ /** Optional profile ID for long-term user tracking. Overrides the global profileId if set. */
56
+ profileId?: string
48
57
  }
49
58
 
50
59
  // Partial user-editable pageview payload
@@ -63,6 +72,9 @@ export interface IPageViewPayload {
63
72
  meta?: {
64
73
  [key: string]: string | number | boolean | null | undefined
65
74
  }
75
+
76
+ /** Optional profile ID for long-term user tracking. Overrides the global profileId if set. */
77
+ profileId?: string
66
78
  }
67
79
 
68
80
  // Partial user-editable error payload
@@ -95,6 +107,41 @@ interface IPerfPayload {
95
107
  ttfb: number
96
108
  }
97
109
 
110
+ /**
111
+ * Options for evaluating feature flags.
112
+ */
113
+ export interface FeatureFlagsOptions {
114
+ /**
115
+ * Optional profile ID for long-term user tracking.
116
+ * If not provided, an anonymous profile ID will be generated server-side based on IP and user agent.
117
+ * Overrides the global profileId if set.
118
+ */
119
+ profileId?: string
120
+ }
121
+
122
+ /**
123
+ * Options for evaluating experiments.
124
+ */
125
+ export interface ExperimentOptions {
126
+ /**
127
+ * Optional profile ID for long-term user tracking.
128
+ * If not provided, an anonymous profile ID will be generated server-side based on IP and user agent.
129
+ * Overrides the global profileId if set.
130
+ */
131
+ profileId?: string
132
+ }
133
+
134
+ /**
135
+ * Cached feature flags and experiments with timestamp.
136
+ */
137
+ interface CachedData {
138
+ flags: Record<string, boolean>
139
+ experiments: Record<string, string>
140
+ timestamp: number
141
+ /** The profileId used when fetching this cached data */
142
+ profileId?: string
143
+ }
144
+
98
145
  /**
99
146
  * The object returned by `trackPageViews()`, used to stop tracking pages.
100
147
  */
@@ -174,14 +221,19 @@ export const defaultActions = {
174
221
  }
175
222
 
176
223
  const DEFAULT_API_HOST = 'https://api.swetrix.com/log'
224
+ const DEFAULT_API_BASE = 'https://api.swetrix.com'
225
+
226
+ // Default cache duration: 5 minutes
227
+ const DEFAULT_CACHE_DURATION = 5 * 60 * 1000
177
228
 
178
229
  export class Lib {
179
230
  private pageData: PageData | null = null
180
231
  private pageViewsOptions?: PageViewsOptions | null = null
181
232
  private errorsOptions?: ErrorOptions | null = null
182
- private perfStatsCollected: Boolean = false
233
+ private perfStatsCollected: boolean = false
183
234
  private activePage: string | null = null
184
235
  private errorListenerExists = false
236
+ private cachedData: CachedData | null = null
185
237
 
186
238
  constructor(private projectID: string, private options?: LibOptions) {
187
239
  this.trackPathChange = this.trackPathChange.bind(this)
@@ -190,7 +242,7 @@ export class Lib {
190
242
  }
191
243
 
192
244
  captureError(event: ErrorEvent): void {
193
- if (typeof this.errorsOptions?.sampleRate === 'number' && this.errorsOptions.sampleRate > Math.random()) {
245
+ if (typeof this.errorsOptions?.sampleRate === 'number' && this.errorsOptions.sampleRate >= Math.random()) {
194
246
  return
195
247
  }
196
248
 
@@ -233,6 +285,7 @@ export class Lib {
233
285
  return {
234
286
  stop: () => {
235
287
  window.removeEventListener('error', this.captureError)
288
+ this.errorListenerExists = false
236
289
  },
237
290
  }
238
291
  }
@@ -279,7 +332,12 @@ export class Lib {
279
332
  const data = {
280
333
  ...event,
281
334
  pid: this.projectID,
282
- pg: this.activePage,
335
+ pg:
336
+ this.activePage ||
337
+ getPath({
338
+ hash: this.pageViewsOptions?.hash,
339
+ search: this.pageViewsOptions?.search,
340
+ }),
283
341
  lc: getLocale(),
284
342
  tz: getTimezone(),
285
343
  ref: getReferrer(),
@@ -288,6 +346,7 @@ export class Lib {
288
346
  ca: getUTMCampaign(),
289
347
  te: getUTMTerm(),
290
348
  co: getUTMContent(),
349
+ profileId: event.profileId ?? this.options?.profileId,
291
350
  }
292
351
  await this.sendRequest('custom', data)
293
352
  }
@@ -362,15 +421,304 @@ export class Lib {
362
421
  }
363
422
  }
364
423
 
424
+ /**
425
+ * Fetches all feature flags and experiments for the project.
426
+ * Results are cached for 5 minutes by default.
427
+ *
428
+ * @param options - Options for evaluating feature flags.
429
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
430
+ * @returns A promise that resolves to a record of flag keys to boolean values.
431
+ */
432
+ async getFeatureFlags(options?: FeatureFlagsOptions, forceRefresh?: boolean): Promise<Record<string, boolean>> {
433
+ if (!isInBrowser()) {
434
+ return {}
435
+ }
436
+
437
+ const requestedProfileId = options?.profileId ?? this.options?.profileId
438
+
439
+ // Check cache first - must match profileId and not be expired
440
+ if (!forceRefresh && this.cachedData) {
441
+ const now = Date.now()
442
+ const isSameProfile = this.cachedData.profileId === requestedProfileId
443
+ if (isSameProfile && now - this.cachedData.timestamp < DEFAULT_CACHE_DURATION) {
444
+ return this.cachedData.flags
445
+ }
446
+ }
447
+
448
+ try {
449
+ await this.fetchFlagsAndExperiments(options)
450
+ return this.cachedData?.flags || {}
451
+ } catch (error) {
452
+ console.warn('[Swetrix] Error fetching feature flags:', error)
453
+ return this.cachedData?.flags || {}
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Internal method to fetch both feature flags and experiments from the API.
459
+ */
460
+ private async fetchFlagsAndExperiments(options?: FeatureFlagsOptions | ExperimentOptions): Promise<void> {
461
+ const apiBase = this.getApiBase()
462
+ const body: { pid: string; profileId?: string } = {
463
+ pid: this.projectID,
464
+ }
465
+
466
+ // Use profileId from options, or fall back to global profileId
467
+ const profileId = options?.profileId ?? this.options?.profileId
468
+ if (profileId) {
469
+ body.profileId = profileId
470
+ }
471
+
472
+ const response = await fetch(`${apiBase}/feature-flag/evaluate`, {
473
+ method: 'POST',
474
+ headers: {
475
+ 'Content-Type': 'application/json',
476
+ },
477
+ body: JSON.stringify(body),
478
+ })
479
+
480
+ if (!response.ok) {
481
+ console.warn('[Swetrix] Failed to fetch feature flags and experiments:', response.status)
482
+ return
483
+ }
484
+
485
+ const data = (await response.json()) as {
486
+ flags: Record<string, boolean>
487
+ experiments?: Record<string, string>
488
+ }
489
+
490
+ // Use profileId from options, or fall back to global profileId
491
+ const cachedProfileId = options?.profileId ?? this.options?.profileId
492
+
493
+ // Update cache with both flags and experiments
494
+ this.cachedData = {
495
+ flags: data.flags || {},
496
+ experiments: data.experiments || {},
497
+ timestamp: Date.now(),
498
+ profileId: cachedProfileId,
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Gets the value of a single feature flag.
504
+ *
505
+ * @param key - The feature flag key.
506
+ * @param options - Options for evaluating the feature flag.
507
+ * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
508
+ * @returns A promise that resolves to the boolean value of the flag.
509
+ */
510
+ async getFeatureFlag(key: string, options?: FeatureFlagsOptions, defaultValue: boolean = false): Promise<boolean> {
511
+ const flags = await this.getFeatureFlags(options)
512
+ return flags[key] ?? defaultValue
513
+ }
514
+
515
+ /**
516
+ * Clears the cached feature flags and experiments, forcing a fresh fetch on the next call.
517
+ */
518
+ clearFeatureFlagsCache(): void {
519
+ this.cachedData = null
520
+ }
521
+
522
+ /**
523
+ * Fetches all A/B test experiments for the project.
524
+ * Results are cached for 5 minutes by default (shared cache with feature flags).
525
+ *
526
+ * @param options - Options for evaluating experiments.
527
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
528
+ * @returns A promise that resolves to a record of experiment IDs to variant keys.
529
+ *
530
+ * @example
531
+ * ```typescript
532
+ * const experiments = await getExperiments()
533
+ * // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
534
+ * ```
535
+ */
536
+ async getExperiments(options?: ExperimentOptions, forceRefresh?: boolean): Promise<Record<string, string>> {
537
+ if (!isInBrowser()) {
538
+ return {}
539
+ }
540
+
541
+ const requestedProfileId = options?.profileId ?? this.options?.profileId
542
+
543
+ // Check cache first - must match profileId and not be expired
544
+ if (!forceRefresh && this.cachedData) {
545
+ const now = Date.now()
546
+ const isSameProfile = this.cachedData.profileId === requestedProfileId
547
+ if (isSameProfile && now - this.cachedData.timestamp < DEFAULT_CACHE_DURATION) {
548
+ return this.cachedData.experiments
549
+ }
550
+ }
551
+
552
+ try {
553
+ await this.fetchFlagsAndExperiments(options)
554
+ return this.cachedData?.experiments || {}
555
+ } catch (error) {
556
+ console.warn('[Swetrix] Error fetching experiments:', error)
557
+ return this.cachedData?.experiments || {}
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Gets the variant key for a specific A/B test experiment.
563
+ *
564
+ * @param experimentId - The experiment ID.
565
+ * @param options - Options for evaluating the experiment.
566
+ * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
567
+ * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * const variant = await getExperiment('checkout-redesign')
572
+ *
573
+ * if (variant === 'new-checkout') {
574
+ * // Show new checkout flow
575
+ * } else {
576
+ * // Show control (original) checkout
577
+ * }
578
+ * ```
579
+ */
580
+ async getExperiment(
581
+ experimentId: string,
582
+ options?: ExperimentOptions,
583
+ defaultVariant: string | null = null,
584
+ ): Promise<string | null> {
585
+ const experiments = await this.getExperiments(options)
586
+ return experiments[experimentId] ?? defaultVariant
587
+ }
588
+
589
+ /**
590
+ * Clears the cached experiments (alias for clearFeatureFlagsCache since they share the same cache).
591
+ */
592
+ clearExperimentsCache(): void {
593
+ this.cachedData = null
594
+ }
595
+
596
+ /**
597
+ * Gets the anonymous profile ID for the current visitor.
598
+ * If profileId was set via init options, returns that.
599
+ * Otherwise, requests server to generate one from IP/UA hash.
600
+ *
601
+ * This ID can be used for revenue attribution with payment providers.
602
+ *
603
+ * @returns A promise that resolves to the profile ID string, or null on error.
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * const profileId = await swetrix.getProfileId()
608
+ *
609
+ * // Pass to Paddle Checkout for revenue attribution
610
+ * Paddle.Checkout.open({
611
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
612
+ * customData: {
613
+ * swetrix_profile_id: profileId,
614
+ * swetrix_session_id: await swetrix.getSessionId()
615
+ * }
616
+ * })
617
+ * ```
618
+ */
619
+ async getProfileId(): Promise<string | null> {
620
+ // If profileId is already set in options, return it
621
+ if (this.options?.profileId) {
622
+ return this.options.profileId
623
+ }
624
+
625
+ if (!isInBrowser()) {
626
+ return null
627
+ }
628
+
629
+ try {
630
+ const apiBase = this.getApiBase()
631
+ const response = await fetch(`${apiBase}/log/profile-id`, {
632
+ method: 'POST',
633
+ headers: {
634
+ 'Content-Type': 'application/json',
635
+ },
636
+ body: JSON.stringify({ pid: this.projectID }),
637
+ })
638
+
639
+ if (!response.ok) {
640
+ return null
641
+ }
642
+
643
+ const data = (await response.json()) as { profileId: string | null }
644
+ return data.profileId
645
+ } catch {
646
+ return null
647
+ }
648
+ }
649
+
650
+ /**
651
+ * Gets the current session ID for the visitor.
652
+ * Session IDs are generated server-side based on IP and user agent.
653
+ *
654
+ * This ID can be used for revenue attribution with payment providers.
655
+ *
656
+ * @returns A promise that resolves to the session ID string, or null on error.
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * const sessionId = await swetrix.getSessionId()
661
+ *
662
+ * // Pass to Paddle Checkout for revenue attribution
663
+ * Paddle.Checkout.open({
664
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
665
+ * customData: {
666
+ * swetrix_profile_id: await swetrix.getProfileId(),
667
+ * swetrix_session_id: sessionId
668
+ * }
669
+ * })
670
+ * ```
671
+ */
672
+ async getSessionId(): Promise<string | null> {
673
+ if (!isInBrowser()) {
674
+ return null
675
+ }
676
+
677
+ try {
678
+ const apiBase = this.getApiBase()
679
+ const response = await fetch(`${apiBase}/log/session-id`, {
680
+ method: 'POST',
681
+ headers: {
682
+ 'Content-Type': 'application/json',
683
+ },
684
+ body: JSON.stringify({ pid: this.projectID }),
685
+ })
686
+
687
+ if (!response.ok) {
688
+ return null
689
+ }
690
+
691
+ const data = (await response.json()) as { sessionId: string | null }
692
+ return data.sessionId
693
+ } catch {
694
+ return null
695
+ }
696
+ }
697
+
698
+ /**
699
+ * Gets the API base URL (without /log suffix).
700
+ */
701
+ private getApiBase(): string {
702
+ if (this.options?.apiURL) {
703
+ // Remove trailing /log if present
704
+ return this.options.apiURL.replace(/\/log\/?$/, '')
705
+ }
706
+ return DEFAULT_API_BASE
707
+ }
708
+
365
709
  private heartbeat(): void {
366
710
  if (!this.pageViewsOptions?.heartbeatOnBackground && document.visibilityState === 'hidden') {
367
711
  return
368
712
  }
369
713
 
370
- const data = {
714
+ const data: { pid: string; profileId?: string } = {
371
715
  pid: this.projectID,
372
716
  }
373
717
 
718
+ if (this.options?.profileId) {
719
+ data.profileId = this.options.profileId
720
+ }
721
+
374
722
  this.sendRequest('hb', data)
375
723
  }
376
724
 
@@ -419,6 +767,7 @@ export class Lib {
419
767
  ca: getUTMCampaign(),
420
768
  te: getUTMTerm(),
421
769
  co: getUTMContent(),
770
+ profileId: this.options?.profileId,
422
771
  ...payload,
423
772
  }
424
773
 
package/src/index.ts CHANGED
@@ -9,6 +9,8 @@ import {
9
9
  defaultActions,
10
10
  IErrorEventPayload,
11
11
  IPageViewPayload,
12
+ FeatureFlagsOptions,
13
+ ExperimentOptions,
12
14
  } from './Lib.js'
13
15
 
14
16
  export let LIB_INSTANCE: Lib | null = null
@@ -120,6 +122,204 @@ export function pageview(options: IPageviewOptions): void {
120
122
  LIB_INSTANCE.submitPageView(options.payload, Boolean(options.unique), {})
121
123
  }
122
124
 
125
+ /**
126
+ * Fetches all feature flags for the project.
127
+ * Results are cached for 5 minutes by default.
128
+ *
129
+ * @param options - Options for evaluating feature flags (visitorId, attributes).
130
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh flags.
131
+ * @returns A promise that resolves to a record of flag keys to boolean values.
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * const flags = await getFeatureFlags({
136
+ * visitorId: 'user-123',
137
+ * attributes: { cc: 'US', dv: 'desktop' }
138
+ * })
139
+ *
140
+ * if (flags['new-checkout']) {
141
+ * // Show new checkout flow
142
+ * }
143
+ * ```
144
+ */
145
+ export async function getFeatureFlags(
146
+ options?: FeatureFlagsOptions,
147
+ forceRefresh?: boolean,
148
+ ): Promise<Record<string, boolean>> {
149
+ if (!LIB_INSTANCE) return {}
150
+
151
+ return LIB_INSTANCE.getFeatureFlags(options, forceRefresh)
152
+ }
153
+
154
+ /**
155
+ * Gets the value of a single feature flag.
156
+ *
157
+ * @param key - The feature flag key.
158
+ * @param options - Options for evaluating the feature flag (visitorId, attributes).
159
+ * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
160
+ * @returns A promise that resolves to the boolean value of the flag.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * const isEnabled = await getFeatureFlag('dark-mode', { visitorId: 'user-123' })
165
+ *
166
+ * if (isEnabled) {
167
+ * // Enable dark mode
168
+ * }
169
+ * ```
170
+ */
171
+ export async function getFeatureFlag(
172
+ key: string,
173
+ options?: FeatureFlagsOptions,
174
+ defaultValue: boolean = false,
175
+ ): Promise<boolean> {
176
+ if (!LIB_INSTANCE) return defaultValue
177
+
178
+ return LIB_INSTANCE.getFeatureFlag(key, options, defaultValue)
179
+ }
180
+
181
+ /**
182
+ * Clears the cached feature flags, forcing a fresh fetch on the next call.
183
+ * Useful when you know the user's context has changed significantly.
184
+ */
185
+ export function clearFeatureFlagsCache(): void {
186
+ if (!LIB_INSTANCE) return
187
+
188
+ LIB_INSTANCE.clearFeatureFlagsCache()
189
+ }
190
+
191
+ /**
192
+ * Fetches all A/B test experiments for the project.
193
+ * Results are cached for 5 minutes by default (shared cache with feature flags).
194
+ *
195
+ * @param options - Options for evaluating experiments.
196
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
197
+ * @returns A promise that resolves to a record of experiment IDs to variant keys.
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * const experiments = await getExperiments()
202
+ * // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
203
+ *
204
+ * // Use the assigned variant
205
+ * const checkoutVariant = experiments['checkout-experiment-id']
206
+ * if (checkoutVariant === 'new-checkout') {
207
+ * showNewCheckout()
208
+ * } else {
209
+ * showOriginalCheckout()
210
+ * }
211
+ * ```
212
+ */
213
+ export async function getExperiments(
214
+ options?: ExperimentOptions,
215
+ forceRefresh?: boolean,
216
+ ): Promise<Record<string, string>> {
217
+ if (!LIB_INSTANCE) return {}
218
+
219
+ return LIB_INSTANCE.getExperiments(options, forceRefresh)
220
+ }
221
+
222
+ /**
223
+ * Gets the variant key for a specific A/B test experiment.
224
+ *
225
+ * @param experimentId - The experiment ID.
226
+ * @param options - Options for evaluating the experiment.
227
+ * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
228
+ * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * const variant = await getExperiment('checkout-redesign-experiment-id')
233
+ *
234
+ * if (variant === 'new-checkout') {
235
+ * // Show new checkout flow
236
+ * showNewCheckout()
237
+ * } else if (variant === 'control') {
238
+ * // Show original checkout (control group)
239
+ * showOriginalCheckout()
240
+ * } else {
241
+ * // Experiment not running or user not included
242
+ * showOriginalCheckout()
243
+ * }
244
+ * ```
245
+ */
246
+ export async function getExperiment(
247
+ experimentId: string,
248
+ options?: ExperimentOptions,
249
+ defaultVariant: string | null = null,
250
+ ): Promise<string | null> {
251
+ if (!LIB_INSTANCE) return defaultVariant
252
+
253
+ return LIB_INSTANCE.getExperiment(experimentId, options, defaultVariant)
254
+ }
255
+
256
+ /**
257
+ * Clears the cached experiments, forcing a fresh fetch on the next call.
258
+ * This is an alias for clearFeatureFlagsCache since experiments and flags share the same cache.
259
+ */
260
+ export function clearExperimentsCache(): void {
261
+ if (!LIB_INSTANCE) return
262
+
263
+ LIB_INSTANCE.clearExperimentsCache()
264
+ }
265
+
266
+ /**
267
+ * Gets the anonymous profile ID for the current visitor.
268
+ * If profileId was set via init options, returns that.
269
+ * Otherwise, requests server to generate one from IP/UA hash.
270
+ *
271
+ * This ID can be used for revenue attribution with payment providers like Paddle.
272
+ *
273
+ * @returns A promise that resolves to the profile ID string, or null on error.
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const profileId = await getProfileId()
278
+ *
279
+ * // Pass to Paddle Checkout for revenue attribution
280
+ * Paddle.Checkout.open({
281
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
282
+ * customData: {
283
+ * swetrix_profile_id: profileId,
284
+ * swetrix_session_id: await getSessionId()
285
+ * }
286
+ * })
287
+ * ```
288
+ */
289
+ export async function getProfileId(): Promise<string | null> {
290
+ if (!LIB_INSTANCE) return null
291
+
292
+ return LIB_INSTANCE.getProfileId()
293
+ }
294
+
295
+ /**
296
+ * Gets the current session ID for the visitor.
297
+ * Session IDs are generated server-side based on IP and user agent.
298
+ *
299
+ * This ID can be used for revenue attribution with payment providers like Paddle.
300
+ *
301
+ * @returns A promise that resolves to the session ID string, or null on error.
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * const sessionId = await getSessionId()
306
+ *
307
+ * // Pass to Paddle Checkout for revenue attribution
308
+ * Paddle.Checkout.open({
309
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
310
+ * customData: {
311
+ * swetrix_profile_id: await getProfileId(),
312
+ * swetrix_session_id: sessionId
313
+ * }
314
+ * })
315
+ * ```
316
+ */
317
+ export async function getSessionId(): Promise<string | null> {
318
+ if (!LIB_INSTANCE) return null
319
+
320
+ return LIB_INSTANCE.getSessionId()
321
+ }
322
+
123
323
  export {
124
324
  LibOptions,
125
325
  TrackEventOptions,
@@ -129,4 +329,6 @@ export {
129
329
  ErrorActions,
130
330
  IErrorEventPayload,
131
331
  IPageViewPayload,
332
+ FeatureFlagsOptions,
333
+ ExperimentOptions,
132
334
  }