@encorekit/web-sdk 1.1.19 → 1.1.21
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/README.md +63 -3
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/umd/encore.min.js +1 -1
- package/dist/umd/encore.min.js.map +1 -1
- package/embed/README.md +1 -1
- package/examples/angular/src/app/app.component.ts +0 -1
- package/examples/angular/src/app/encore.service.ts +1 -2
- package/examples/ios-webview/EncoreWebViewBridge.swift +3 -10
- package/examples/ios-webview/README.md +0 -20
- package/examples/react/.env +1 -0
- package/examples/react/src/App.tsx +1 -1
- package/examples/svelte/src/App.svelte +0 -2
- package/examples/vanilla-js/index.html +0 -2
- package/examples/vue/src/App.vue +0 -2
- package/package.json +10 -8
- package/types/analytics/AnalyticsClient.d.ts +11 -3
- package/types/analytics/AnalyticsClient.d.ts.map +1 -1
- package/types/analytics/events.d.ts +30 -1
- package/types/analytics/events.d.ts.map +1 -1
- package/types/analytics/models.d.ts +11 -8
- package/types/analytics/models.d.ts.map +1 -1
- package/types/analytics/transport.d.ts +23 -0
- package/types/analytics/transport.d.ts.map +1 -0
- package/types/api/APIClient.d.ts +1 -1
- package/types/api/APIClient.d.ts.map +1 -1
- package/types/api/contract.d.ts +32 -0
- package/types/api/contract.d.ts.map +1 -0
- package/types/api/generated/types.d.ts +4519 -0
- package/types/api/generated/types.d.ts.map +1 -0
- package/types/api/models.d.ts +45 -128
- package/types/api/models.d.ts.map +1 -1
- package/types/core/Configuration.d.ts +2 -2
- package/types/core/Configuration.d.ts.map +1 -1
- package/types/core/DisplayResolver.d.ts +121 -0
- package/types/core/DisplayResolver.d.ts.map +1 -0
- package/types/core/Encore.d.ts +77 -4
- package/types/core/Encore.d.ts.map +1 -1
- package/types/core/PlacementBuilder.d.ts +14 -9
- package/types/core/PlacementBuilder.d.ts.map +1 -1
- package/types/core/TransactionStore.d.ts +35 -0
- package/types/core/TransactionStore.d.ts.map +1 -0
- package/types/core/version.d.ts +1 -1
- package/types/experiments/ExperimentManager.d.ts +43 -0
- package/types/experiments/ExperimentManager.d.ts.map +1 -0
- package/types/experiments/ExposureOutbox.d.ts +37 -0
- package/types/experiments/ExposureOutbox.d.ts.map +1 -0
- package/types/experiments/bucketing.d.ts +33 -0
- package/types/experiments/bucketing.d.ts.map +1 -0
- package/types/experiments/types.d.ts +60 -0
- package/types/experiments/types.d.ts.map +1 -0
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/react.d.ts +1 -3
- package/types/react.d.ts.map +1 -1
- package/types/types.d.ts +284 -18
- package/types/types.d.ts.map +1 -1
- package/types/ui/OfferCard.d.ts.map +1 -1
- package/types/ui/OfferCarousel.d.ts +14 -3
- package/types/ui/OfferCarousel.d.ts.map +1 -1
- package/types/ui/OfferModal.d.ts +6 -0
- package/types/ui/OfferModal.d.ts.map +1 -1
- package/types/ui/PrimerScreen.d.ts +34 -0
- package/types/ui/PrimerScreen.d.ts.map +1 -0
- package/types/ui/RedemptionScreen.d.ts +57 -0
- package/types/ui/RedemptionScreen.d.ts.map +1 -0
- package/types/ui/ThankYouLayout.d.ts +35 -0
- package/types/ui/ThankYouLayout.d.ts.map +1 -0
- package/types/ui/brandComponents.d.ts +45 -0
- package/types/ui/brandComponents.d.ts.map +1 -0
- package/types/ui/safeImageSrc.d.ts +15 -0
- package/types/ui/safeImageSrc.d.ts.map +1 -0
- package/types/ui/styles.d.ts.map +1 -1
- package/examples/react/.env.local +0 -1
- package/types/__tests__/PlacementBuilder.test.d.ts +0 -2
- package/types/__tests__/PlacementBuilder.test.d.ts.map +0 -1
- package/types/__tests__/SuccessScreen.test.d.ts +0 -2
- package/types/__tests__/SuccessScreen.test.d.ts.map +0 -1
- package/types/__tests__/react.test.d.ts +0 -2
- package/types/__tests__/react.test.d.ts.map +0 -1
- package/types/__tests__/styles.test.d.ts +0 -2
- package/types/__tests__/styles.test.d.ts.map +0 -1
- package/types/ui/SuccessScreen.d.ts +0 -35
- package/types/ui/SuccessScreen.d.ts.map +0 -1
package/types/core/Encore.d.ts
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
* Main Encore SDK Class
|
|
3
3
|
* Singleton class that serves as the public API entry point
|
|
4
4
|
*/
|
|
5
|
-
import type { EncoreConfig, UserAttributes, EntitlementType, EntitlementScope, EventHandler, Unsubscribe, PresentationOptions, PresentationResult, PlacementOptions, VerificationStatus, PlacementBuilder as IPlacementBuilder } from '../types';
|
|
5
|
+
import type { EncoreConfig, UserAttributes, EntitlementType, EntitlementScope, EventHandler, Unsubscribe, PresentationOptions, PresentationResult, PlacementOptions, VerificationStatus, PlacementBuilder as IPlacementBuilder, Transaction, ShowResult, RedeemOptions, RedeemResult } from '../types';
|
|
6
|
+
import type { NCLExperimentState } from '../experiments/types';
|
|
6
7
|
export declare class EncoreSDK {
|
|
7
8
|
#private;
|
|
8
9
|
constructor();
|
|
9
10
|
/**
|
|
10
11
|
* Configure the SDK with API key and options
|
|
11
12
|
*/
|
|
12
|
-
configure(config: EncoreConfig):
|
|
13
|
+
configure(config: EncoreConfig): boolean;
|
|
13
14
|
/**
|
|
14
15
|
* Identify user with custom user ID and optionally set attributes
|
|
15
16
|
*/
|
|
@@ -18,6 +19,17 @@ export declare class EncoreSDK {
|
|
|
18
19
|
* Get current user ID
|
|
19
20
|
*/
|
|
20
21
|
getCurrentUserId(): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* The persistent account identity this user's transactions, exposures, and
|
|
24
|
+
* subscription events are bound to (the deferred-redemption join key).
|
|
25
|
+
*
|
|
26
|
+
* Resolves to the `identify()`-provided stable id if set, otherwise a
|
|
27
|
+
* persistent anonymous UUID kept in localStorage (surviving reloads and
|
|
28
|
+
* same-origin navigations). It is the same value the backend joins on as
|
|
29
|
+
* `app_account_id` / `iap_links.appTransactionId`. Call `identify(stableId)`
|
|
30
|
+
* for cross-navigation / cross-device robustness.
|
|
31
|
+
*/
|
|
32
|
+
getAppAccountId(): string | null;
|
|
21
33
|
/**
|
|
22
34
|
* Reset SDK state (clear user ID, attributes, entitlements, storage)
|
|
23
35
|
*/
|
|
@@ -26,6 +38,33 @@ export declare class EncoreSDK {
|
|
|
26
38
|
* Get SDK version
|
|
27
39
|
*/
|
|
28
40
|
getVersion(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Whether configure() has been called successfully.
|
|
43
|
+
*/
|
|
44
|
+
isConfigured(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Read-only snapshot of the live configuration, or null if not configured.
|
|
47
|
+
* Lets a host app/harness show what the SDK is actually using (vs. what a
|
|
48
|
+
* form is about to apply) and which endpoints the environment resolves to.
|
|
49
|
+
*/
|
|
50
|
+
getConfiguration(): {
|
|
51
|
+
apiKey: string;
|
|
52
|
+
environment: string;
|
|
53
|
+
baseURL: string;
|
|
54
|
+
analyticsBaseURL: string;
|
|
55
|
+
logLevel: string;
|
|
56
|
+
redemptionMode: 'deferred' | 'immediate';
|
|
57
|
+
} | null;
|
|
58
|
+
/**
|
|
59
|
+
* Read-only snapshot of the NCL (Net Conversion Lift) experiment state, or
|
|
60
|
+
* `null` if the SDK is not configured. Lets a host app or harness observe the
|
|
61
|
+
* deterministic cohort assignment — the config epoch, the user's bucket and
|
|
62
|
+
* cohort, and whether the A/B exposure has been logged this epoch — without
|
|
63
|
+
* affecting it (no exposure is logged, no rollout is changed). Reports
|
|
64
|
+
* `enabled: false` / `cohort: 'notEnrolled'` when no NCL experiment is active
|
|
65
|
+
* for this app. Async because deterministic bucketing uses WebCrypto SHA-256.
|
|
66
|
+
*/
|
|
67
|
+
getExperimentState(): Promise<NCLExperimentState | null>;
|
|
29
68
|
/**
|
|
30
69
|
* Set log level
|
|
31
70
|
*/
|
|
@@ -43,10 +82,44 @@ export declare class EncoreSDK {
|
|
|
43
82
|
*/
|
|
44
83
|
getUserAttributes(): UserAttributes;
|
|
45
84
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
85
|
+
* Show offers to the user — phase 1 of the two-phase flow.
|
|
86
|
+
*
|
|
87
|
+
* Resolves when the user CLAIMS an offer. In 'deferred' mode (default) a transaction
|
|
88
|
+
* record is created and returned with NO redirect — call redeem() after your own
|
|
89
|
+
* conversion to send the user to the advertiser. In 'immediate' mode the legacy
|
|
90
|
+
* one-step flow runs (redirect + success screen inline). Resolves 'dismissed' if
|
|
91
|
+
* the user closes the carousel, or 'unavailable' if there are no eligible offers.
|
|
92
|
+
*
|
|
93
|
+
* @param placementId Optional placement key — recorded on the transaction so
|
|
94
|
+
* `placement(id).redeem()` can resolve exactly this transaction later.
|
|
95
|
+
*/
|
|
96
|
+
show(options?: PresentationOptions, placementOptions?: PlacementOptions, placementId?: string): Promise<ShowResult>;
|
|
97
|
+
/**
|
|
98
|
+
* @deprecated Use `show()` (and `redeem()` for the deferred flow). Kept for
|
|
99
|
+
* back-compat; maps the new `ShowResult` onto the legacy `PresentationResult`.
|
|
100
|
+
* In 'deferred' mode a transaction resolves here as not-granted (no redirect yet) —
|
|
101
|
+
* migrate to `show()`/`redeem()` for the two-phase flow.
|
|
48
102
|
*/
|
|
49
103
|
presentOffer(options?: PresentationOptions, placementOptions?: PlacementOptions): Promise<PresentationResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Redeem a previously-claimed offer — phase 2 of the two-phase flow. Call this
|
|
106
|
+
* after your own conversion succeeds. Renders the redemption screen for the
|
|
107
|
+
* pending transaction; on the user's CTA tap it sends them to the advertiser (the
|
|
108
|
+
* redirect fires inside that gesture, dodging popup blockers) and emits the
|
|
109
|
+
* provisional grant.
|
|
110
|
+
*
|
|
111
|
+
* Transaction resolution (internal — no handle crosses the API): `placement`
|
|
112
|
+
* resolves the transaction made at THAT placement (never crossing placements →
|
|
113
|
+
* `none` if absent); else the single most-recent transaction for this user.
|
|
114
|
+
*/
|
|
115
|
+
redeem(options?: RedeemOptions): Promise<RedeemResult>;
|
|
116
|
+
/**
|
|
117
|
+
* The pending (unredeemed) transaction for this user, resolved like redeem() but
|
|
118
|
+
* without showing any UI — lets a host check whether there's something to redeem
|
|
119
|
+
* and render its own CTA. Local-only for P0 (server recovery by app_account_id
|
|
120
|
+
* lands in P1). Pass a placement id to scope to that placement's transaction.
|
|
121
|
+
*/
|
|
122
|
+
getPendingTransaction(placementId?: string): Transaction | null;
|
|
50
123
|
/**
|
|
51
124
|
* @deprecated Client-side entitlement validation removed for security.
|
|
52
125
|
* Check entitlements on your server by calling Encore's server-side API.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Encore.d.ts","sourceRoot":"","sources":["../../src/core/Encore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAEhB,kBAAkB,EAElB,gBAAgB,IAAI,iBAAiB,
|
|
1
|
+
{"version":3,"file":"Encore.d.ts","sourceRoot":"","sources":["../../src/core/Encore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAEhB,kBAAkB,EAElB,gBAAgB,IAAI,iBAAiB,EACrC,WAAW,EACX,UAAU,EACV,aAAa,EACb,YAAY,EAGb,MAAM,UAAU,CAAC;AAelB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAgC/D,qBAAa,SAAS;;;IA4CpB;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO;IA+GxC;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI;IAoD3D;;OAEG;IACH,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAIjC;;;;;;;;;OASG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,KAAK,IAAI,IAAI;IA2Db;;OAEG;IACH,UAAU,IAAI,MAAM;IAIpB;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;;;OAIG;IACH,gBAAgB,IAAI;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,gBAAgB,EAAE,MAAM,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,UAAU,GAAG,WAAW,CAAC;KAC1C,GAAG,IAAI;IAYR;;;;;;;;OAQG;IACG,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAK9D;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAI1C;;OAEG;IACG,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC;IAgB5C;;OAEG;IACH,iBAAiB,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI;IAwBnD;;OAEG;IACH,iBAAiB,IAAI,cAAc;IAInC;;;;;;;;;;;OAWG;IACG,IAAI,CACR,OAAO,CAAC,EAAE,mBAAmB,EAC7B,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,UAAU,CAAC;IAsCtB;;;;;OAKG;IACG,YAAY,CAChB,OAAO,CAAC,EAAE,mBAAmB,EAC7B,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,OAAO,CAAC,kBAAkB,CAAC;IAgB9B;;;;;;;;;;OAUG;IACG,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAkF5D;;;;;OAKG;IACH,qBAAqB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAO/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO;IAQ3E;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAiBtE;;;OAGG;IACG,mBAAmB,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAqC/F;;;;OAIG;IACH,EAAE,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,WAAW;IAS9E;;;;OAIG;IACH,kBAAkB,CAChB,YAAY,EAAE,eAAe,EAC7B,MAAM,CAAC,EAAE,gBAAgB,GACxB;QACD,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,KAAK,WAAW,CAAC;KACnE;IAaD;;;;;;;;OAQG;IACH,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,iBAAiB;CA88BlG;AAGD,eAAO,MAAM,MAAM,WAAkB,CAAC"}
|
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
* Placement Builder
|
|
3
3
|
* Fluent API for configuring and presenting offers
|
|
4
4
|
*/
|
|
5
|
-
import type { PlacementBuilder as IPlacementBuilder, PlacementOptions,
|
|
6
|
-
export type PresentationExecutor = (options?: PlacementOptions) => Promise<
|
|
5
|
+
import type { PlacementBuilder as IPlacementBuilder, PlacementOptions, ShowResult, RedeemResult, NotGrantedReason, ShowOptions } from '../types';
|
|
6
|
+
export type PresentationExecutor = (options?: PlacementOptions, showOptions?: ShowOptions) => Promise<ShowResult>;
|
|
7
|
+
export type RedeemExecutor = (placementId: string) => Promise<RedeemResult>;
|
|
7
8
|
export declare class PlacementBuilder implements IPlacementBuilder {
|
|
8
9
|
#private;
|
|
9
|
-
constructor(id: string, executor: PresentationExecutor, options?: PlacementOptions);
|
|
10
|
-
/**
|
|
11
|
-
* Set callback for when entitlement is granted
|
|
12
|
-
*/
|
|
13
|
-
onGranted(callback: (entitlement: EntitlementType) => void): PlacementBuilder;
|
|
10
|
+
constructor(id: string, executor: PresentationExecutor, options?: PlacementOptions, redeemExecutor?: RedeemExecutor);
|
|
14
11
|
/**
|
|
15
12
|
* Set callback for when user declines or offer not granted
|
|
16
13
|
*/
|
|
@@ -20,8 +17,16 @@ export declare class PlacementBuilder implements IPlacementBuilder {
|
|
|
20
17
|
*/
|
|
21
18
|
onLoadingStateChange(callback: (isLoading: boolean) => void): PlacementBuilder;
|
|
22
19
|
/**
|
|
23
|
-
* Show the offer and execute the presentation flow
|
|
20
|
+
* Show the offer and execute the presentation flow.
|
|
21
|
+
*
|
|
22
|
+
* @param options Per-call show options (e.g. the `receipt` for the 'thankYou' layout).
|
|
23
|
+
*/
|
|
24
|
+
show(options?: ShowOptions): Promise<ShowResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Redeem the claim made at THIS placement — phase 2 of the two-phase flow.
|
|
27
|
+
* Resolves the claim recorded for this placement id, renders the redemption
|
|
28
|
+
* screen, and fires the advertiser redirect on the user's CTA. See Encore.redeem().
|
|
24
29
|
*/
|
|
25
|
-
|
|
30
|
+
redeem(): Promise<RedeemResult>;
|
|
26
31
|
}
|
|
27
32
|
//# sourceMappingURL=PlacementBuilder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlacementBuilder.d.ts","sourceRoot":"","sources":["../../src/core/PlacementBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,gBAAgB,IAAI,iBAAiB,EACrC,gBAAgB,EAChB,
|
|
1
|
+
{"version":3,"file":"PlacementBuilder.d.ts","sourceRoot":"","sources":["../../src/core/PlacementBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,gBAAgB,IAAI,iBAAiB,EACrC,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACZ,MAAM,UAAU,CAAC;AAIlB,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,CAAC,EAAE,gBAAgB,EAC1B,WAAW,CAAC,EAAE,WAAW,KACtB,OAAO,CAAC,UAAU,CAAC,CAAC;AACzB,MAAM,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAE5E,qBAAa,gBAAiB,YAAW,iBAAiB;;gBAStD,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,oBAAoB,EAC9B,OAAO,CAAC,EAAE,gBAAgB,EAC1B,cAAc,CAAC,EAAE,cAAc;IAQjC;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAAG,gBAAgB;IAK5E;;OAEG;IACH,oBAAoB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,GAAG,gBAAgB;IAK9E;;;;OAIG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAoDtD;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;CAgBtC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransactionStore
|
|
3
|
+
*
|
|
4
|
+
* Local persistence for the two-phase (transaction -> redeem) flow. Backed by
|
|
5
|
+
* StorageManager (localStorage-first), it holds:
|
|
6
|
+
* - transactions: transactionId -> Transaction
|
|
7
|
+
* - placements: placementId -> transactionId (the per-placement live-transaction map)
|
|
8
|
+
* - latest: transactionId of the most recently created transaction
|
|
9
|
+
*
|
|
10
|
+
* This is the fast path; the server transaction record (keyed by app_account_id, carrying
|
|
11
|
+
* placementId) is the durable source of truth for cross-page / post-localStorage-loss
|
|
12
|
+
* recovery. Expired or redeemed transactions are filtered out of the lookups.
|
|
13
|
+
*/
|
|
14
|
+
import type { Transaction } from '../types';
|
|
15
|
+
import type { StorageManager } from './StorageManager';
|
|
16
|
+
export declare class TransactionStore {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(storage: StorageManager);
|
|
19
|
+
/** Persist a new transaction, updating the placement map and the latest pointer. */
|
|
20
|
+
save(transaction: Transaction): void;
|
|
21
|
+
/** Resolve a transaction by exact transaction id (any status). */
|
|
22
|
+
getByTransactionId(transactionId: string): Transaction | null;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the LIVE transaction made at a placement. Returns null if none/expired/redeemed —
|
|
25
|
+
* never crosses to another placement's transaction.
|
|
26
|
+
*/
|
|
27
|
+
getByPlacement(placementId: string): Transaction | null;
|
|
28
|
+
/** Resolve the single most-recent LIVE transaction for this user (the flat redeem() fallback). */
|
|
29
|
+
getLatest(): Transaction | null;
|
|
30
|
+
/** Mark a transaction redeemed (idempotent). Frees its placement slot. */
|
|
31
|
+
markRedeemed(transactionId: string): void;
|
|
32
|
+
/** Clear all transactions (used by reset()). */
|
|
33
|
+
clear(): void;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=TransactionStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransactionStore.d.ts","sourceRoot":"","sources":["../../src/core/TransactionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAavD,qBAAa,gBAAgB;;gBAGf,OAAO,EAAE,cAAc;IAwBnC,oFAAoF;IACpF,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAepC,kEAAkE;IAClE,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAI7D;;;OAGG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAQvD,kGAAkG;IAClG,SAAS,IAAI,WAAW,GAAG,IAAI;IAa/B,0EAA0E;IAC1E,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IA8BzC,gDAAgD;IAChD,KAAK,IAAI,IAAI;CAGd"}
|
package/types/core/version.d.ts
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns NCL (Net Conversion Lift) cohort assignment + exposure logging for the web SDK.
|
|
3
|
+
*
|
|
4
|
+
* Cohort is deterministic in (appAccountId, assignmentVersion) — the same person
|
|
5
|
+
* gets the same cohort across devices and reloads (see `bucketing.ts` for the
|
|
6
|
+
* byte-exact cross-platform contract). Bumping `assignmentVersion` server-side
|
|
7
|
+
* re-buckets everyone (a clean epoch).
|
|
8
|
+
*
|
|
9
|
+
* Mirrors iOS `ExperimentManager.swift` / Android `ExperimentManager.kt`.
|
|
10
|
+
*/
|
|
11
|
+
import type { StorageManager } from '../core/StorageManager';
|
|
12
|
+
import type { Cohort, NCLExperimentConfig, NCLExperimentState } from './types';
|
|
13
|
+
import type { ExposureOutbox } from './ExposureOutbox';
|
|
14
|
+
export declare class ExperimentManager {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(storage: StorageManager, getAppAccountId: () => string | null, exposureOutbox: ExposureOutbox);
|
|
17
|
+
/** Route the remote config's `experiments.ncl` block into the manager. */
|
|
18
|
+
setConfig(config: NCLExperimentConfig | null | undefined): void;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the current NCL cohort. Returns `notEnrolled` (→ normal offer flow,
|
|
21
|
+
* no exposure) when there is no appAccountId, no NCL config, or the experiment
|
|
22
|
+
* is disabled. Any unexpected error also degrades to `notEnrolled`.
|
|
23
|
+
*/
|
|
24
|
+
getCohort(): Promise<Cohort>;
|
|
25
|
+
/**
|
|
26
|
+
* Log the exposure for `treatment`/`control` at the offer trigger — the A/B
|
|
27
|
+
* denominator. No-op for `notEnrolled`. Deduped per assignment epoch so reloads
|
|
28
|
+
* don't double-count: emits once per (user, assignmentVersion), re-emitting only
|
|
29
|
+
* when the server bumps the epoch. (Deliberately stronger than mobile, which
|
|
30
|
+
* emits per show; web reloads would otherwise inflate the denominator.)
|
|
31
|
+
*/
|
|
32
|
+
logExposure(cohort: Cohort): void;
|
|
33
|
+
/**
|
|
34
|
+
* Read-only diagnostic snapshot (host apps / the demo harness). Reports the
|
|
35
|
+
* authoritative `cohort` (via `getCohort`, so it reflects exactly what the
|
|
36
|
+
* offer flow uses) alongside the computed `bucket` and the per-epoch exposure
|
|
37
|
+
* marker. Recomputing the bucket re-runs the SHA-256 purely for display — it
|
|
38
|
+
* never logs exposure or alters the rollout. `bucket` is `null` whenever a
|
|
39
|
+
* cohort can't be bucketed (no appAccountId, disabled, or no WebCrypto).
|
|
40
|
+
*/
|
|
41
|
+
getState(): Promise<NCLExperimentState>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ExperimentManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExperimentManager.d.ts","sourceRoot":"","sources":["../../src/experiments/ExperimentManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,OAAO,KAAK,EAAE,MAAM,EAAiB,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC9F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,qBAAa,iBAAiB;;gBAO1B,OAAO,EAAE,cAAc,EACvB,eAAe,EAAE,MAAM,MAAM,GAAG,IAAI,EACpC,cAAc,EAAE,cAAc;IAOhC,0EAA0E;IAG1E,SAAS,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAI/D;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAmDlC;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAgCjC;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,kBAAkB,CAAC;CA+B9C"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reliable delivery for the NCL exposure event — the A/B denominator.
|
|
3
|
+
*
|
|
4
|
+
* Exposure MUST survive tab-close, so unlike the fire-and-forget AnalyticsClient
|
|
5
|
+
* this path is storage-backed and retried.
|
|
6
|
+
*
|
|
7
|
+
* Delivery primitive: `fetch(url, { keepalive: true })` — NOT `navigator.sendBeacon`.
|
|
8
|
+
* The ingest endpoint authenticates on the `X-Analytics-Key` request header, and
|
|
9
|
+
* sendBeacon cannot set headers (a beacon would arrive unauthenticated). keepalive
|
|
10
|
+
* fetch survives document unload like a beacon AND carries the auth header.
|
|
11
|
+
*
|
|
12
|
+
* Reliability model:
|
|
13
|
+
* - enqueue: persist the envelope, then keepalive-POST; remove on 2xx.
|
|
14
|
+
* - flush: replay every persisted envelope (on SDK init + on pagehide /
|
|
15
|
+
* visibilitychange:hidden) so a send that died with the tab retries.
|
|
16
|
+
* - envelopes carry a STABLE event_id → retries are idempotent backend-side.
|
|
17
|
+
* - an in-memory in-flight set avoids duplicate concurrent sends.
|
|
18
|
+
* - all storage / window / document access is SSR- and failure-guarded (fail-open).
|
|
19
|
+
*/
|
|
20
|
+
import type { AnalyticsEvent } from '../analytics/models';
|
|
21
|
+
import type { StorageManager } from '../core/StorageManager';
|
|
22
|
+
export declare class ExposureOutbox {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(apiKey: string, analyticsBaseURL: string, storage: StorageManager);
|
|
25
|
+
/**
|
|
26
|
+
* Persist an exposure event and attempt delivery. `eventId` must be stable for a
|
|
27
|
+
* given (user, epoch) so retries de-duplicate backend-side. Returns whether the
|
|
28
|
+
* envelope durably landed in the configured storage tier: a quota-driven
|
|
29
|
+
* fallback-to-memory write is invisible to a fresh read, so the caller can avoid
|
|
30
|
+
* marking the epoch logged (and re-enqueue on the next trigger) instead of
|
|
31
|
+
* silently dropping the exposure.
|
|
32
|
+
*/
|
|
33
|
+
enqueue(event: AnalyticsEvent, eventId: string): boolean;
|
|
34
|
+
/** Replay every persisted envelope (init recovery + unload flush). */
|
|
35
|
+
flush(): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=ExposureOutbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExposureOutbox.d.ts","sourceRoot":"","sources":["../../src/experiments/ExposureOutbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,qBAAqB,CAAC;AAEzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,qBAAa,cAAc;;gBAMb,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc;IAO7E;;;;;;;OAOG;IACH,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAaxD,sEAAsE;IACtE,KAAK,IAAI,IAAI;CAoDd"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic NCL cohort bucketing — the byte-exact cross-platform core.
|
|
3
|
+
*
|
|
4
|
+
* Same (appAccountId, assignmentVersion) MUST land in the same bucket on web,
|
|
5
|
+
* iOS, and Android, or the NCL revenue-lift math breaks cross-platform.
|
|
6
|
+
*
|
|
7
|
+
* Verified byte-for-byte against:
|
|
8
|
+
* - iOS Encore/.../Experiments/ExperimentManager.swift:81-87
|
|
9
|
+
* - Android features/experiments/ExperimentManager.kt:88-98
|
|
10
|
+
* - Fixture android/.../ExperimentManagerTest.kt:40-75 (15 reference pairs)
|
|
11
|
+
*
|
|
12
|
+
* Algorithm:
|
|
13
|
+
* input = "<appAccountId>:<version>" (UTF-8)
|
|
14
|
+
* digest = SHA-256(input) (via crypto.subtle)
|
|
15
|
+
* hash64 = digest[0..7] as big-endian UInt64 (BigInt — Number can't hold it)
|
|
16
|
+
* bucket = hash64 mod 100 (∈ [0, 99])
|
|
17
|
+
*/
|
|
18
|
+
import type { Cohort } from './types';
|
|
19
|
+
/**
|
|
20
|
+
* Compute the deterministic bucket [0, 99] for (appAccountId, version).
|
|
21
|
+
*
|
|
22
|
+
* Uses BigInt for the 64-bit accumulation: the top 8 bytes of a SHA-256 digest
|
|
23
|
+
* routinely exceed `Number.MAX_SAFE_INTEGER` (2^53), so a plain `number` would
|
|
24
|
+
* silently lose precision and diverge from iOS/Android. This is the footgun the
|
|
25
|
+
* BigInt path exists to avoid.
|
|
26
|
+
*/
|
|
27
|
+
export declare function computeBucket(appAccountId: string, version: number): Promise<number>;
|
|
28
|
+
/**
|
|
29
|
+
* Classify a bucket into a cohort: `bucket < rolloutPct` → treatment, else control.
|
|
30
|
+
* (rolloutPct 0 → always control, 100 → always treatment.)
|
|
31
|
+
*/
|
|
32
|
+
export declare function cohortForBucket(bucket: number, rolloutPct: number): Cohort;
|
|
33
|
+
//# sourceMappingURL=bucketing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucketing.d.ts","sourceRoot":"","sources":["../../src/experiments/bucketing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAU1F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1E"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experiment domain types (NCL — Net Conversion Lift A/B).
|
|
3
|
+
*
|
|
4
|
+
* Cohort string values are the cross-platform BigQuery join literals and MUST
|
|
5
|
+
* match iOS/Android verbatim ('treatment' | 'control' | 'notEnrolled').
|
|
6
|
+
*/
|
|
7
|
+
import type * as Contract from '../api/contract';
|
|
8
|
+
/**
|
|
9
|
+
* NCL cohort assignment.
|
|
10
|
+
*
|
|
11
|
+
* - `treatment`: user sees the Encore offer (normal flow).
|
|
12
|
+
* - `control`: Ghost Trigger — no UI shown, exposure logged (the A/B denominator).
|
|
13
|
+
* - `notEnrolled`: experiment disabled, config unavailable, or no appAccountId —
|
|
14
|
+
* falls back to normal behaviour (offers shown, NO exposure logged).
|
|
15
|
+
*/
|
|
16
|
+
export type Cohort = 'treatment' | 'control' | 'notEnrolled';
|
|
17
|
+
/**
|
|
18
|
+
* The `experiments.ncl` block of the remote config response.
|
|
19
|
+
* `enabled === false` makes the manager a no-op (everyone notEnrolled → normal flow).
|
|
20
|
+
*
|
|
21
|
+
* Derived from the contract (not hand-declared): it is fed verbatim from
|
|
22
|
+
* fetchConfig().experiments.ncl with no mapping, so a field going optional/
|
|
23
|
+
* nullable in the backend contract should surface here as a compile error rather
|
|
24
|
+
* than silent drift. `null` (contract "not configured") is handled by consumers.
|
|
25
|
+
*/
|
|
26
|
+
export type NCLExperimentConfig = NonNullable<NonNullable<Contract.ConfigResponse['experiments']>['ncl']>;
|
|
27
|
+
/**
|
|
28
|
+
* Persisted assignment cache. Cohort is a pure function of
|
|
29
|
+
* (appAccountId, assignmentVersion); this only avoids re-hashing on every show.
|
|
30
|
+
* Both keys are stored so a mismatch on EITHER forces a re-bucket: a bumped
|
|
31
|
+
* `assignmentVersion` (new epoch) or a changed `appAccountId` (e.g. an anonymous
|
|
32
|
+
* visitor calling `identify()` mid-session — web storage is per-origin and
|
|
33
|
+
* shared across identities, unlike the device-stable mobile id).
|
|
34
|
+
*/
|
|
35
|
+
export interface NCLAssignment {
|
|
36
|
+
cohort: Cohort;
|
|
37
|
+
appAccountId: string;
|
|
38
|
+
assignmentVersion: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read-only diagnostic snapshot of the NCL experiment state, surfaced by
|
|
42
|
+
* `Encore.getExperimentState()` for host apps and the demo harness. Exposes the
|
|
43
|
+
* full deterministic chain — the config epoch (`assignmentVersion`, `rolloutPct`),
|
|
44
|
+
* the resolved `appAccountId`, its computed `bucket` ∈ [0, 99], the resulting
|
|
45
|
+
* `cohort`, and whether the A/B `exposure` has been logged this epoch — without
|
|
46
|
+
* mutating any of it. `enabled: false` / `cohort: 'notEnrolled'` when no NCL
|
|
47
|
+
* experiment is active for the app. `bucket` is `null` when it can't be computed
|
|
48
|
+
* (no appAccountId, experiment disabled, or an insecure context with no WebCrypto).
|
|
49
|
+
*/
|
|
50
|
+
export interface NCLExperimentState {
|
|
51
|
+
experiment: string;
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
rolloutPct: number | null;
|
|
54
|
+
assignmentVersion: number | null;
|
|
55
|
+
appAccountId: string | null;
|
|
56
|
+
bucket: number | null;
|
|
57
|
+
cohort: Cohort;
|
|
58
|
+
exposureLogged: boolean;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/experiments/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,CAAC;AAE7D;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAC3C,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAC3D,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;CACzB"}
|
package/types/index.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export { Encore, Encore as default } from './core/Encore';
|
|
|
7
7
|
export type * from './types';
|
|
8
8
|
export type * from './api/models';
|
|
9
9
|
export type { EntitlementChangedEvent, EntitlementEventMap } from './core/EntitlementManager';
|
|
10
|
+
export type { Cohort, NCLExperimentState } from './experiments/types';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM,eAAe,CAAC;AAG1D,mBAAmB,SAAS,CAAC;AAG7B,mBAAmB,cAAc,CAAC;AAGlC,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM,eAAe,CAAC;AAG1D,mBAAmB,SAAS,CAAC;AAG7B,mBAAmB,cAAc,CAAC;AAGlC,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAG9F,YAAY,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/types/react.d.ts
CHANGED
|
@@ -2,9 +2,7 @@ import { type ReactNode } from 'react';
|
|
|
2
2
|
import type { EncoreConfig } from '@encorekit/web-sdk';
|
|
3
3
|
type EncoreProviderProps = {
|
|
4
4
|
children: ReactNode;
|
|
5
|
-
} &
|
|
6
|
-
userId?: string;
|
|
7
|
-
};
|
|
5
|
+
} & EncoreConfig;
|
|
8
6
|
export declare function EncoreProvider({ children, ...config }: EncoreProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
9
7
|
export declare function useEncore(): import("./core/Encore").EncoreSDK;
|
|
10
8
|
export { Encore } from '@encorekit/web-sdk';
|
package/types/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,YAAY,CAAC;AAIjB,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,EAAE,mBAAmB,2CAa1E;AAED,wBAAgB,SAAS,sCAExB;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,mBAAmB,oBAAoB,CAAC"}
|