@cimplify/sdk 0.103.0 → 0.104.1
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/dist/advanced.d.mts +3 -7
- package/dist/advanced.d.ts +3 -7
- package/dist/advanced.js +23 -23
- package/dist/advanced.mjs +4 -4
- package/dist/{client-DHBwvh7U.d.ts → catalogue-BlQ85Uma.d.mts} +492 -748
- package/dist/{client-BfpNjJ_a.d.mts → catalogue-C8TqOj9K.d.ts} +492 -748
- package/dist/{payment-H5LoIvXU.d.mts → checkout-BqK9XUz5.d.mts} +4338 -1495
- package/dist/{payment-H5LoIvXU.d.ts → checkout-BqK9XUz5.d.ts} +4338 -1495
- package/dist/{chunk-GOHSXBKW.mjs → chunk-33VCYWVL.mjs} +25 -1
- package/dist/{chunk-C2JSNJUC.mjs → chunk-6G3ANKQA.mjs} +1080 -369
- package/dist/{chunk-QOWPGS7Z.js → chunk-BWEIMKLF.js} +572 -17
- package/dist/{chunk-JSWJ4XDE.js → chunk-DQEXY542.js} +1630 -824
- package/dist/{chunk-CNN7Z2IP.js → chunk-EV2EVOYS.js} +27 -0
- package/dist/{chunk-VIL6IQCG.js → chunk-HYFA67A7.js} +1084 -373
- package/dist/{chunk-H7ACMHM3.mjs → chunk-L5SNEU2O.mjs} +553 -16
- package/dist/{chunk-JG5CQNCB.js → chunk-RBNBSQI7.mjs} +221 -19
- package/dist/{chunk-VNPEYLB2.mjs → chunk-SFUJMS5E.js} +265 -2
- package/dist/{chunk-M4URVKHE.mjs → chunk-U54CWUUT.mjs} +11 -2
- package/dist/{chunk-GERXZQSX.mjs → chunk-UFOWPN3D.mjs} +967 -582
- package/dist/{chunk-J6PNHDHL.mjs → chunk-V4457OKB.mjs} +1580 -785
- package/dist/{chunk-R6OLQ33I.js → chunk-WXREIBFW.js} +1041 -654
- package/dist/{chunk-NNG6IFFX.js → chunk-XAUSDMMZ.js} +13 -3
- package/dist/{client-DpqRobY9.d.mts → client-DhXeiUrf.d.ts} +2 -2
- package/dist/{client-Dqg2mc-7.d.ts → client-mXiUrV4X.d.mts} +2 -2
- package/dist/{index-C8uDqLwJ.d.ts → index-B0AdZvrM.d.mts} +9 -4
- package/dist/{index-e2y6AK70.d.mts → index-CK8Nk5uQ.d.ts} +9 -4
- package/dist/index-DPWI5XcU.d.ts +8 -0
- package/dist/index-DVfUh-ci.d.mts +8 -0
- package/dist/index.d.mts +34 -15
- package/dist/index.d.ts +34 -15
- package/dist/index.js +306 -133
- package/dist/index.mjs +7 -6
- package/dist/mock/cli.mjs +1494 -395
- package/dist/mock/library.js +1220 -361
- package/dist/mock/library.mjs +1220 -361
- package/dist/mock/msw.js +1220 -361
- package/dist/mock/msw.mjs +1220 -361
- package/dist/{price-C2RqscMY.d.ts → price-6hQIIac9.d.ts} +1 -1
- package/dist/{price-BvePgDXe.d.mts → price-BUDv1rI7.d.mts} +1 -1
- package/dist/react.d.mts +10 -5
- package/dist/react.d.ts +10 -5
- package/dist/react.js +279 -225
- package/dist/react.mjs +140 -86
- package/dist/{server-DoXIpSd7.d.ts → server-DJ0mSs9i.d.mts} +33 -57
- package/dist/{server-C7FyLdrz.d.mts → server-DSTX76Ki.d.ts} +33 -57
- package/dist/server.d.mts +3 -4
- package/dist/server.d.ts +3 -4
- package/dist/server.js +12 -11
- package/dist/server.mjs +6 -5
- package/dist/styles.css +2 -2
- package/dist/testing/msw.d.mts +2 -2
- package/dist/testing/msw.d.ts +2 -2
- package/dist/testing/msw.js +4 -4
- package/dist/testing/msw.mjs +3 -3
- package/dist/testing/suite.d.mts +5 -6
- package/dist/testing/suite.d.ts +5 -6
- package/dist/testing/suite.js +29 -28
- package/dist/testing/suite.mjs +10 -9
- package/dist/testing.d.mts +2217 -14
- package/dist/testing.d.ts +2217 -14
- package/dist/testing.js +173 -83
- package/dist/testing.mjs +28 -10
- package/dist/utils.d.mts +3 -4
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +35 -31
- package/dist/utils.mjs +2 -2
- package/package.json +9 -9
- package/registry/chat-widget.json +1 -1
- package/registry/order-history.json +1 -1
- package/registry/order-summary.json +1 -1
- package/dist/link-cuNVYc2s.d.mts +0 -297
- package/dist/link-cuNVYc2s.d.ts +0 -297
|
@@ -1,5 +1,333 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { messagePreview } from './chunk-U4MW6PYU.mjs';
|
|
2
|
+
import { ok, err, safe, generateIdempotencyKey, createDefaultPaymentAuthorizationAdapterRegistry, CatalogueQueries, CartOperations, CheckoutService, OrderQueries, LinkService, AuthService, BusinessService, InventoryService, SchedulingService, LiteService, FxService, createElements } from './chunk-V4457OKB.mjs';
|
|
3
|
+
import { PURCHASE_INTENT_RESOLVE_STATUS, PURCHASE_INTENT_CONFIRM_STATUS, PURCHASE_INTENT_RESOLUTION_KIND, PURCHASE_INTENT_ACTION_DECISION, CimplifyError, PURCHASE_INTENT_ERROR_CODE, ErrorCode, IdempotencyMismatchError, enrichError } from './chunk-RBNBSQI7.mjs';
|
|
4
|
+
|
|
5
|
+
// src/types/observability.ts
|
|
6
|
+
var SESSION_CHANGE_SOURCE = {
|
|
7
|
+
RESPONSE: "response",
|
|
8
|
+
MANUAL: "manual",
|
|
9
|
+
CLEAR: "clear"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/utils/purchase-intent-resolver.ts
|
|
13
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
14
|
+
var DEFAULT_MAX_POLL_ATTEMPTS = 60;
|
|
15
|
+
var DEFAULT_MAX_ACTION_STEPS = 4;
|
|
16
|
+
var DEFAULT_MAX_TERMS_REVIEW_STEPS = 4;
|
|
17
|
+
function nonnegativeInteger(value, fallback) {
|
|
18
|
+
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
19
|
+
return Math.max(0, Math.floor(value));
|
|
20
|
+
}
|
|
21
|
+
function abortError() {
|
|
22
|
+
const error = new Error("Purchase-intent resolution was cancelled.");
|
|
23
|
+
error.name = "AbortError";
|
|
24
|
+
return error;
|
|
25
|
+
}
|
|
26
|
+
async function abortableWait(milliseconds, signal) {
|
|
27
|
+
if (signal?.aborted) throw abortError();
|
|
28
|
+
if (milliseconds <= 0) return;
|
|
29
|
+
await new Promise((resolve, reject) => {
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
signal?.removeEventListener("abort", onAbort);
|
|
32
|
+
resolve();
|
|
33
|
+
}, milliseconds);
|
|
34
|
+
const onAbort = () => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
reject(abortError());
|
|
37
|
+
};
|
|
38
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
var PurchaseIntentResolver = class {
|
|
42
|
+
constructor(dependencies) {
|
|
43
|
+
this.confirmation = dependencies.confirmation;
|
|
44
|
+
this.wait = dependencies.wait ?? abortableWait;
|
|
45
|
+
}
|
|
46
|
+
async resolve(intentId, options) {
|
|
47
|
+
const pollIntervalMs = nonnegativeInteger(
|
|
48
|
+
options.pollIntervalMs,
|
|
49
|
+
DEFAULT_POLL_INTERVAL_MS
|
|
50
|
+
);
|
|
51
|
+
const maxPollAttempts = nonnegativeInteger(
|
|
52
|
+
options.maxPollAttempts,
|
|
53
|
+
DEFAULT_MAX_POLL_ATTEMPTS
|
|
54
|
+
);
|
|
55
|
+
const maxActionSteps = nonnegativeInteger(options.maxActionSteps, DEFAULT_MAX_ACTION_STEPS);
|
|
56
|
+
const maxTermsReviewSteps = nonnegativeInteger(
|
|
57
|
+
options.maxTermsReviewSteps,
|
|
58
|
+
DEFAULT_MAX_TERMS_REVIEW_STEPS
|
|
59
|
+
);
|
|
60
|
+
let request = options.confirmation;
|
|
61
|
+
let pollAttempts = 0;
|
|
62
|
+
let actionSteps = 0;
|
|
63
|
+
let termsReviewSteps = 0;
|
|
64
|
+
this.ensureActive(options.signal);
|
|
65
|
+
this.notify(options, PURCHASE_INTENT_RESOLVE_STATUS.CONFIRMING);
|
|
66
|
+
let responseResult = options.initialResponse ? ok(options.initialResponse) : await this.confirmation.confirm(intentId, request, { signal: options.signal });
|
|
67
|
+
while (responseResult.ok) {
|
|
68
|
+
this.ensureActive(options.signal);
|
|
69
|
+
const response = responseResult.value;
|
|
70
|
+
switch (response.status) {
|
|
71
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.ORDER_CREATED:
|
|
72
|
+
this.notify(options, PURCHASE_INTENT_RESOLVE_STATUS.COMPLETED, response);
|
|
73
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.COMPLETED, response });
|
|
74
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.PAYMENT_FAILED:
|
|
75
|
+
this.notify(options, PURCHASE_INTENT_RESOLVE_STATUS.FAILED, response);
|
|
76
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.FAILED, response });
|
|
77
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.PAYMENT_OUTCOME_UNKNOWN:
|
|
78
|
+
this.notify(options, PURCHASE_INTENT_RESOLVE_STATUS.OUTCOME_UNKNOWN, response);
|
|
79
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.OUTCOME_UNKNOWN, response });
|
|
80
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.COLLECTION_PROCESSING:
|
|
81
|
+
if (pollAttempts >= maxPollAttempts) {
|
|
82
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.TIMED_OUT, response });
|
|
83
|
+
}
|
|
84
|
+
pollAttempts += 1;
|
|
85
|
+
this.notify(options, PURCHASE_INTENT_RESOLVE_STATUS.POLLING, response);
|
|
86
|
+
await this.wait(pollIntervalMs, options.signal);
|
|
87
|
+
responseResult = await this.confirmation.confirm(intentId, request, {
|
|
88
|
+
signal: options.signal
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.REDIRECT_REQUIRED:
|
|
92
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.CARD_POPUP_REQUIRED:
|
|
93
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.AUTHORIZATION_REQUIRED: {
|
|
94
|
+
this.notify(options, PURCHASE_INTENT_RESOLVE_STATUS.AWAITING_ACTION, response);
|
|
95
|
+
if (!options.onActionRequired || actionSteps >= maxActionSteps) {
|
|
96
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.ACTION_REQUIRED, response });
|
|
97
|
+
}
|
|
98
|
+
actionSteps += 1;
|
|
99
|
+
const decision = await options.onActionRequired(response);
|
|
100
|
+
if (decision === PURCHASE_INTENT_ACTION_DECISION.STOP) {
|
|
101
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.ACTION_REQUIRED, response });
|
|
102
|
+
}
|
|
103
|
+
if (typeof decision === "object" && decision.kind === PURCHASE_INTENT_ACTION_DECISION.SUBMIT) {
|
|
104
|
+
if (!this.confirmation.submitAction) {
|
|
105
|
+
return err(
|
|
106
|
+
new CimplifyError(
|
|
107
|
+
PURCHASE_INTENT_ERROR_CODE.ACTION_UNSUPPORTED,
|
|
108
|
+
"This confirmation transport cannot submit purchase-intent actions.",
|
|
109
|
+
false
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
responseResult = await this.confirmation.submitAction(
|
|
114
|
+
intentId,
|
|
115
|
+
response.attempt_id,
|
|
116
|
+
decision.request,
|
|
117
|
+
{ signal: options.signal }
|
|
118
|
+
);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (decision !== PURCHASE_INTENT_ACTION_DECISION.CONTINUE) {
|
|
122
|
+
return err(
|
|
123
|
+
new CimplifyError(
|
|
124
|
+
PURCHASE_INTENT_ERROR_CODE.ACTION_UNSUPPORTED,
|
|
125
|
+
"The action callback returned an unsupported decision.",
|
|
126
|
+
false
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
await this.wait(pollIntervalMs, options.signal);
|
|
131
|
+
responseResult = await this.confirmation.confirm(intentId, request, {
|
|
132
|
+
signal: options.signal
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.TERMS_CHANGED: {
|
|
137
|
+
this.notify(options, PURCHASE_INTENT_RESOLVE_STATUS.REVIEWING_TERMS, response);
|
|
138
|
+
if (!options.onTermsChanged || termsReviewSteps >= maxTermsReviewSteps) {
|
|
139
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.REVIEW_REQUIRED, response });
|
|
140
|
+
}
|
|
141
|
+
termsReviewSteps += 1;
|
|
142
|
+
const replacementRequest = await options.onTermsChanged(response);
|
|
143
|
+
if (!replacementRequest) {
|
|
144
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.REVIEW_REQUIRED, response });
|
|
145
|
+
}
|
|
146
|
+
request = replacementRequest;
|
|
147
|
+
responseResult = await this.confirmation.confirm(intentId, request, {
|
|
148
|
+
signal: options.signal
|
|
149
|
+
});
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.SELECTION_UNAVAILABLE:
|
|
153
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.OFFER_ENDED:
|
|
154
|
+
case PURCHASE_INTENT_CONFIRM_STATUS.SOURCE_UNAVAILABLE:
|
|
155
|
+
return ok({ kind: PURCHASE_INTENT_RESOLUTION_KIND.REVIEW_REQUIRED, response });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return responseResult;
|
|
159
|
+
}
|
|
160
|
+
ensureActive(signal) {
|
|
161
|
+
if (signal?.aborted) throw abortError();
|
|
162
|
+
}
|
|
163
|
+
notify(options, status, response) {
|
|
164
|
+
try {
|
|
165
|
+
options.onStatusChange?.(status, response);
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// src/purchase-intent.ts
|
|
172
|
+
var PURCHASE_INTENT_ROUTE = {
|
|
173
|
+
DIRECT_COLLECTION: "/api/v1/purchase-intents",
|
|
174
|
+
HOSTED_SESSION_COLLECTION: "/v1/checkout/sessions",
|
|
175
|
+
PREPARE: "prepare",
|
|
176
|
+
REFRESH: "refresh",
|
|
177
|
+
CONFIRM: "confirm",
|
|
178
|
+
REPLAY: "replay",
|
|
179
|
+
ATTEMPTS: "attempts",
|
|
180
|
+
ACTIONS: "actions"
|
|
181
|
+
};
|
|
182
|
+
function directIntentPath(intentId) {
|
|
183
|
+
const normalized = intentId.trim();
|
|
184
|
+
if (!normalized) throw new TypeError("intentId must not be empty");
|
|
185
|
+
return `${PURCHASE_INTENT_ROUTE.DIRECT_COLLECTION}/${encodeURIComponent(normalized)}`;
|
|
186
|
+
}
|
|
187
|
+
function hostedSessionPath(sessionId) {
|
|
188
|
+
const normalized = sessionId.trim();
|
|
189
|
+
if (!normalized) throw new TypeError("sessionId must not be empty");
|
|
190
|
+
return `${PURCHASE_INTENT_ROUTE.HOSTED_SESSION_COLLECTION}/${encodeURIComponent(normalized)}`;
|
|
191
|
+
}
|
|
192
|
+
var PurchaseIntentService = class {
|
|
193
|
+
constructor(transport) {
|
|
194
|
+
this.transport = transport;
|
|
195
|
+
this.resolver = new PurchaseIntentResolver({ confirmation: this });
|
|
196
|
+
}
|
|
197
|
+
mint(request, options) {
|
|
198
|
+
return safe(
|
|
199
|
+
this.transport.post(PURCHASE_INTENT_ROUTE.DIRECT_COLLECTION, request, {
|
|
200
|
+
idempotencyKey: request.client_intent_key,
|
|
201
|
+
signal: options?.signal,
|
|
202
|
+
timeoutMs: options?.timeoutMs
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
get(intentId, options) {
|
|
207
|
+
return safe(
|
|
208
|
+
this.transport.get(this.intentPath(intentId), {
|
|
209
|
+
signal: options?.signal,
|
|
210
|
+
timeoutMs: options?.timeoutMs
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
refresh(intentId, expectedRevision, options) {
|
|
215
|
+
const request = {
|
|
216
|
+
expected_intent_revision: expectedRevision
|
|
217
|
+
};
|
|
218
|
+
return safe(
|
|
219
|
+
this.transport.post(
|
|
220
|
+
`${this.intentPath(intentId)}/${PURCHASE_INTENT_ROUTE.REFRESH}`,
|
|
221
|
+
request,
|
|
222
|
+
{
|
|
223
|
+
signal: options?.signal,
|
|
224
|
+
timeoutMs: options?.timeoutMs
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
confirm(intentId, request, options) {
|
|
230
|
+
return safe(
|
|
231
|
+
this.transport.post(
|
|
232
|
+
`${this.intentPath(intentId)}/${PURCHASE_INTENT_ROUTE.CONFIRM}`,
|
|
233
|
+
request,
|
|
234
|
+
{
|
|
235
|
+
idempotencyKey: request.idempotency_key,
|
|
236
|
+
signal: options?.signal,
|
|
237
|
+
timeoutMs: options?.timeoutMs
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
submitAction(intentId, attemptId, request, options) {
|
|
243
|
+
const normalizedAttemptId = attemptId.trim();
|
|
244
|
+
if (!normalizedAttemptId) throw new TypeError("attemptId must not be empty");
|
|
245
|
+
return safe(
|
|
246
|
+
this.transport.post(
|
|
247
|
+
`${this.intentPath(intentId)}/${PURCHASE_INTENT_ROUTE.ATTEMPTS}/${encodeURIComponent(normalizedAttemptId)}/${PURCHASE_INTENT_ROUTE.ACTIONS}`,
|
|
248
|
+
request,
|
|
249
|
+
{
|
|
250
|
+
signal: options?.signal,
|
|
251
|
+
timeoutMs: options?.timeoutMs
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
resolve(intentId, options) {
|
|
257
|
+
return this.resolver.resolve(intentId, options);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Bind subsequent preparation/confirmation to one hosted checkout-session
|
|
261
|
+
* owner. The session ID is a route authority, not an intent-owner rewrite.
|
|
262
|
+
*/
|
|
263
|
+
forHostedSession(sessionId) {
|
|
264
|
+
return new HostedPurchaseIntentService(this.transport, sessionId);
|
|
265
|
+
}
|
|
266
|
+
intentPath(intentId) {
|
|
267
|
+
return directIntentPath(intentId);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var HostedPurchaseIntentService = class {
|
|
271
|
+
constructor(transport, sessionId) {
|
|
272
|
+
this.transport = transport;
|
|
273
|
+
this.sessionPath = hostedSessionPath(sessionId);
|
|
274
|
+
this.resolver = new PurchaseIntentResolver({ confirmation: this });
|
|
275
|
+
}
|
|
276
|
+
prepare(request, options) {
|
|
277
|
+
return safe(
|
|
278
|
+
this.transport.post(
|
|
279
|
+
`${this.sessionPath}/${PURCHASE_INTENT_ROUTE.PREPARE}`,
|
|
280
|
+
request,
|
|
281
|
+
{
|
|
282
|
+
idempotencyKey: request.idempotency_key,
|
|
283
|
+
signal: options?.signal,
|
|
284
|
+
timeoutMs: options?.timeoutMs
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
confirm(_intentId, request, options) {
|
|
290
|
+
return safe(
|
|
291
|
+
this.transport.post(
|
|
292
|
+
`${this.sessionPath}/${PURCHASE_INTENT_ROUTE.CONFIRM}`,
|
|
293
|
+
request,
|
|
294
|
+
{
|
|
295
|
+
idempotencyKey: request.idempotency_key,
|
|
296
|
+
signal: options?.signal,
|
|
297
|
+
timeoutMs: options?.timeoutMs
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
replay(options) {
|
|
303
|
+
return safe(
|
|
304
|
+
this.transport.get(
|
|
305
|
+
`${this.sessionPath}/${PURCHASE_INTENT_ROUTE.REPLAY}`,
|
|
306
|
+
{
|
|
307
|
+
signal: options?.signal,
|
|
308
|
+
timeoutMs: options?.timeoutMs
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
submitAction(_intentId, attemptId, request, options) {
|
|
314
|
+
const normalized = attemptId.trim();
|
|
315
|
+
if (!normalized) throw new TypeError("attemptId must not be empty");
|
|
316
|
+
return safe(
|
|
317
|
+
this.transport.post(
|
|
318
|
+
`${this.sessionPath}/${PURCHASE_INTENT_ROUTE.ATTEMPTS}/${encodeURIComponent(normalized)}/${PURCHASE_INTENT_ROUTE.ACTIONS}`,
|
|
319
|
+
request,
|
|
320
|
+
{
|
|
321
|
+
signal: options?.signal,
|
|
322
|
+
timeoutMs: options?.timeoutMs
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
resolve(intentId, options) {
|
|
328
|
+
return this.resolver.resolve(intentId, options);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
3
331
|
|
|
4
332
|
// src/store-credit.ts
|
|
5
333
|
var StoreCreditQueries = class {
|
|
@@ -17,12 +345,13 @@ var StoreCreditQueries = class {
|
|
|
17
345
|
|
|
18
346
|
// src/internal/analytics-session.ts
|
|
19
347
|
var ANALYTICS_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
20
|
-
var
|
|
21
|
-
var ANALYTICS_SESSION_STATE_VERSION =
|
|
348
|
+
var ANALYTICS_SESSION_STORAGE_KEY_PREFIX = "cimplify_analytics_session:v2:";
|
|
349
|
+
var ANALYTICS_SESSION_STATE_VERSION = 2;
|
|
22
350
|
var MICROSECONDS_PER_MILLISECOND = 1e3;
|
|
23
351
|
var MICROSECONDS_PER_SECOND = 1e6;
|
|
24
352
|
var AnalyticsSessionCoordinator = class {
|
|
25
|
-
constructor() {
|
|
353
|
+
constructor(identity) {
|
|
354
|
+
this.identity = identity;
|
|
26
355
|
this.fallbackState = null;
|
|
27
356
|
}
|
|
28
357
|
beginEvent(nowMs = Date.now()) {
|
|
@@ -60,7 +389,7 @@ var AnalyticsSessionCoordinator = class {
|
|
|
60
389
|
reset() {
|
|
61
390
|
this.fallbackState = null;
|
|
62
391
|
try {
|
|
63
|
-
sessionStorage.removeItem(
|
|
392
|
+
sessionStorage.removeItem(this.storageKey());
|
|
64
393
|
} catch {
|
|
65
394
|
}
|
|
66
395
|
}
|
|
@@ -70,11 +399,11 @@ var AnalyticsSessionCoordinator = class {
|
|
|
70
399
|
}
|
|
71
400
|
read() {
|
|
72
401
|
try {
|
|
73
|
-
const serialized = sessionStorage.getItem(
|
|
402
|
+
const serialized = sessionStorage.getItem(this.storageKey());
|
|
74
403
|
if (!serialized) return this.fallbackState;
|
|
75
404
|
const state = JSON.parse(serialized);
|
|
76
405
|
if (!isPersistedSession(state)) {
|
|
77
|
-
sessionStorage.removeItem(
|
|
406
|
+
sessionStorage.removeItem(this.storageKey());
|
|
78
407
|
return this.fallbackState;
|
|
79
408
|
}
|
|
80
409
|
this.fallbackState = state;
|
|
@@ -86,10 +415,13 @@ var AnalyticsSessionCoordinator = class {
|
|
|
86
415
|
write(state) {
|
|
87
416
|
this.fallbackState = state;
|
|
88
417
|
try {
|
|
89
|
-
sessionStorage.setItem(
|
|
418
|
+
sessionStorage.setItem(this.storageKey(), JSON.stringify(state));
|
|
90
419
|
} catch {
|
|
91
420
|
}
|
|
92
421
|
}
|
|
422
|
+
storageKey() {
|
|
423
|
+
return `${ANALYTICS_SESSION_STORAGE_KEY_PREFIX}${encodeURIComponent(this.identity)}`;
|
|
424
|
+
}
|
|
93
425
|
};
|
|
94
426
|
function newSession(nowMs) {
|
|
95
427
|
return {
|
|
@@ -156,11 +488,127 @@ function generateSessionId() {
|
|
|
156
488
|
|
|
157
489
|
// src/activity.ts
|
|
158
490
|
var VISITOR_ID_STORAGE_KEY = "cimplify_visitor_id";
|
|
491
|
+
var ACTIVITY_RETRY_POLICY = {
|
|
492
|
+
maxRetries: 1,
|
|
493
|
+
jitterRatio: 0.2
|
|
494
|
+
};
|
|
495
|
+
var PAGE_VIEW_TRACKER_KEY = /* @__PURE__ */ Symbol.for("@cimplify/sdk.page-view-tracker-hub");
|
|
496
|
+
var PAGE_VIEW_TRACKER_VERSION = 2;
|
|
497
|
+
function trackerHost() {
|
|
498
|
+
return window;
|
|
499
|
+
}
|
|
500
|
+
function currentWindowTrackerHub() {
|
|
501
|
+
const candidate = trackerHost()[PAGE_VIEW_TRACKER_KEY];
|
|
502
|
+
return candidate?.version === PAGE_VIEW_TRACKER_VERSION && candidate.registrations instanceof Map && typeof candidate.recordAll === "function" ? candidate : null;
|
|
503
|
+
}
|
|
504
|
+
function weakReference(target) {
|
|
505
|
+
const WeakReference = globalThis.WeakRef;
|
|
506
|
+
if (WeakReference) return new WeakReference(target);
|
|
507
|
+
return { deref: () => target };
|
|
508
|
+
}
|
|
509
|
+
function latestLiveOwner(registration) {
|
|
510
|
+
let latest = null;
|
|
511
|
+
for (const [token, reference] of registration.owners) {
|
|
512
|
+
const owner = reference.deref();
|
|
513
|
+
if (!owner || owner.released) {
|
|
514
|
+
registration.owners.delete(token);
|
|
515
|
+
} else {
|
|
516
|
+
latest = owner;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return latest;
|
|
520
|
+
}
|
|
521
|
+
function recordRegistration(registration, path) {
|
|
522
|
+
if (path === registration.lastPath) return;
|
|
523
|
+
const owner = registration.activeOwner && !registration.activeOwner.released ? registration.activeOwner : latestLiveOwner(registration);
|
|
524
|
+
if (!owner) return;
|
|
525
|
+
registration.activeOwner = owner;
|
|
526
|
+
registration.lastPath = path;
|
|
527
|
+
owner.recorder(path);
|
|
528
|
+
}
|
|
529
|
+
function createWindowTrackerHub() {
|
|
530
|
+
const originalPushState = window.history.pushState;
|
|
531
|
+
const originalReplaceState = window.history.replaceState;
|
|
532
|
+
const hub = {
|
|
533
|
+
version: PAGE_VIEW_TRACKER_VERSION,
|
|
534
|
+
registrations: /* @__PURE__ */ new Map(),
|
|
535
|
+
originalPushState,
|
|
536
|
+
originalReplaceState
|
|
537
|
+
};
|
|
538
|
+
hub.recordAll = () => {
|
|
539
|
+
const path = window.location.pathname;
|
|
540
|
+
for (const registration of hub.registrations.values()) {
|
|
541
|
+
recordRegistration(registration, path);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
hub.patchedPushState = function(...args) {
|
|
545
|
+
originalPushState.apply(this, args);
|
|
546
|
+
hub.recordAll();
|
|
547
|
+
};
|
|
548
|
+
hub.patchedReplaceState = function(...args) {
|
|
549
|
+
originalReplaceState.apply(this, args);
|
|
550
|
+
hub.recordAll();
|
|
551
|
+
};
|
|
552
|
+
hub.popstateListener = () => hub.recordAll();
|
|
553
|
+
window.history.pushState = hub.patchedPushState;
|
|
554
|
+
window.history.replaceState = hub.patchedReplaceState;
|
|
555
|
+
window.addEventListener("popstate", hub.popstateListener);
|
|
556
|
+
trackerHost()[PAGE_VIEW_TRACKER_KEY] = hub;
|
|
557
|
+
return hub;
|
|
558
|
+
}
|
|
559
|
+
function pageViewTrackerIdentity(client) {
|
|
560
|
+
return JSON.stringify([client.getBaseUrl(), client.getPublicKey().trim()]);
|
|
561
|
+
}
|
|
562
|
+
function acquireWindowPageViewTracker(identity, recorder) {
|
|
563
|
+
const hub = currentWindowTrackerHub() ?? createWindowTrackerHub();
|
|
564
|
+
let registration = hub.registrations.get(identity);
|
|
565
|
+
const isNewIdentity = !registration;
|
|
566
|
+
if (!registration) {
|
|
567
|
+
registration = { owners: /* @__PURE__ */ new Map(), activeOwner: null, lastPath: null };
|
|
568
|
+
hub.registrations.set(identity, registration);
|
|
569
|
+
}
|
|
570
|
+
const owner = {
|
|
571
|
+
token: Symbol(identity),
|
|
572
|
+
recorder,
|
|
573
|
+
released: false
|
|
574
|
+
};
|
|
575
|
+
registration.owners.set(owner.token, weakReference(owner));
|
|
576
|
+
registration.activeOwner = owner;
|
|
577
|
+
if (isNewIdentity) {
|
|
578
|
+
recordRegistration(registration, window.location.pathname);
|
|
579
|
+
}
|
|
580
|
+
let released = false;
|
|
581
|
+
return () => {
|
|
582
|
+
if (released) return;
|
|
583
|
+
released = true;
|
|
584
|
+
owner.released = true;
|
|
585
|
+
registration.owners.delete(owner.token);
|
|
586
|
+
if (registration.activeOwner === owner) {
|
|
587
|
+
registration.activeOwner = latestLiveOwner(registration);
|
|
588
|
+
}
|
|
589
|
+
if (registration.activeOwner) return;
|
|
590
|
+
if (hub.registrations.get(identity) === registration) {
|
|
591
|
+
hub.registrations.delete(identity);
|
|
592
|
+
}
|
|
593
|
+
if (hub.registrations.size !== 0) return;
|
|
594
|
+
window.removeEventListener("popstate", hub.popstateListener);
|
|
595
|
+
if (window.history.pushState === hub.patchedPushState) {
|
|
596
|
+
window.history.pushState = hub.originalPushState;
|
|
597
|
+
}
|
|
598
|
+
if (window.history.replaceState === hub.patchedReplaceState) {
|
|
599
|
+
window.history.replaceState = hub.originalReplaceState;
|
|
600
|
+
}
|
|
601
|
+
if (currentWindowTrackerHub() === hub) {
|
|
602
|
+
delete trackerHost()[PAGE_VIEW_TRACKER_KEY];
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
159
606
|
var ActivityService = class {
|
|
160
607
|
constructor(client) {
|
|
161
608
|
this.client = client;
|
|
162
|
-
this.
|
|
609
|
+
this.localGeneration = 0;
|
|
163
610
|
this.bootstrapRequest = null;
|
|
611
|
+
this.sessions = this.createSessionCoordinator();
|
|
164
612
|
}
|
|
165
613
|
/**
|
|
166
614
|
* Record session start (landing, referrer, UTM, device). Fire-and-forget,
|
|
@@ -170,7 +618,7 @@ var ActivityService = class {
|
|
|
170
618
|
startSession() {
|
|
171
619
|
if (typeof window === "undefined") return;
|
|
172
620
|
const session = this.sessions.beginEvent();
|
|
173
|
-
void this.bootstrapSession(session).catch(() => {
|
|
621
|
+
void this.bootstrapSession(session, false, this.captureGeneration()).catch(() => {
|
|
174
622
|
});
|
|
175
623
|
}
|
|
176
624
|
/** Rebind an existing analytics session after the server authenticates a customer. */
|
|
@@ -178,13 +626,19 @@ var ActivityService = class {
|
|
|
178
626
|
if (typeof window === "undefined") return;
|
|
179
627
|
const session = this.sessions.current();
|
|
180
628
|
if (!session) return;
|
|
181
|
-
void this.bootstrapSession(session, true).catch(() => {
|
|
629
|
+
void this.bootstrapSession(session, true, this.captureGeneration()).catch(() => {
|
|
182
630
|
});
|
|
183
631
|
}
|
|
184
632
|
/** A signed-out browser starts a fresh anonymous analytics session. */
|
|
185
633
|
resetSession() {
|
|
634
|
+
this.invalidatePendingActivity();
|
|
186
635
|
this.sessions.reset();
|
|
187
|
-
|
|
636
|
+
}
|
|
637
|
+
/** Drop the old storefront's analytics state before binding this service to the new one. */
|
|
638
|
+
resetForStorefrontIdentityChange() {
|
|
639
|
+
this.invalidatePendingActivity();
|
|
640
|
+
this.sessions.reset();
|
|
641
|
+
this.sessions = this.createSessionCoordinator();
|
|
188
642
|
}
|
|
189
643
|
buildSessionPayload(session) {
|
|
190
644
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -206,6 +660,7 @@ var ActivityService = class {
|
|
|
206
660
|
utm_campaign: utm("campaign"),
|
|
207
661
|
utm_term: utm("term"),
|
|
208
662
|
utm_content: utm("content"),
|
|
663
|
+
social_commerce_ref: params.get("social_commerce_ref") || void 0,
|
|
209
664
|
screen_width: window.screen?.width,
|
|
210
665
|
screen_height: window.screen?.height,
|
|
211
666
|
viewport_width: window.innerWidth,
|
|
@@ -279,32 +734,12 @@ var ActivityService = class {
|
|
|
279
734
|
autoTrackPageViews() {
|
|
280
735
|
if (typeof window === "undefined") return () => {
|
|
281
736
|
};
|
|
282
|
-
|
|
283
|
-
const record = () => {
|
|
284
|
-
const path = window.location.pathname;
|
|
285
|
-
if (path === lastPath) return;
|
|
286
|
-
lastPath = path;
|
|
737
|
+
return acquireWindowPageViewTracker(pageViewTrackerIdentity(this.client), (path) => {
|
|
287
738
|
try {
|
|
288
739
|
this.trackPageView({ pagePath: path });
|
|
289
740
|
} catch {
|
|
290
741
|
}
|
|
291
|
-
};
|
|
292
|
-
const { pushState, replaceState } = window.history;
|
|
293
|
-
window.history.pushState = function(...args) {
|
|
294
|
-
pushState.apply(this, args);
|
|
295
|
-
record();
|
|
296
|
-
};
|
|
297
|
-
window.history.replaceState = function(...args) {
|
|
298
|
-
replaceState.apply(this, args);
|
|
299
|
-
record();
|
|
300
|
-
};
|
|
301
|
-
window.addEventListener("popstate", record);
|
|
302
|
-
record();
|
|
303
|
-
return () => {
|
|
304
|
-
window.history.pushState = pushState;
|
|
305
|
-
window.history.replaceState = replaceState;
|
|
306
|
-
window.removeEventListener("popstate", record);
|
|
307
|
-
};
|
|
742
|
+
});
|
|
308
743
|
}
|
|
309
744
|
sendEvents(events, session) {
|
|
310
745
|
if (typeof window === "undefined") return;
|
|
@@ -312,29 +747,59 @@ var ActivityService = class {
|
|
|
312
747
|
});
|
|
313
748
|
}
|
|
314
749
|
async postEvents(events, session) {
|
|
315
|
-
|
|
316
|
-
await this.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
events
|
|
320
|
-
|
|
750
|
+
const generation = this.captureGeneration();
|
|
751
|
+
await this.bootstrapSession(session, false, generation);
|
|
752
|
+
if (!this.isCurrentGeneration(generation)) return;
|
|
753
|
+
await this.client.post(
|
|
754
|
+
"/api/v1/activity/events",
|
|
755
|
+
{
|
|
756
|
+
analytics_session_id: session.id,
|
|
757
|
+
occurred_at: session.occurredAt,
|
|
758
|
+
events
|
|
759
|
+
},
|
|
760
|
+
{ retryPolicy: ACTIVITY_RETRY_POLICY }
|
|
761
|
+
);
|
|
321
762
|
}
|
|
322
|
-
bootstrapSession(session, force = false) {
|
|
763
|
+
bootstrapSession(session, force = false, generation = this.captureGeneration()) {
|
|
323
764
|
if (!force && !session.requiresBootstrap) return Promise.resolve();
|
|
324
|
-
if (!force && this.bootstrapRequest?.sessionId === session.id) {
|
|
765
|
+
if (!force && this.bootstrapRequest?.sessionId === session.id && sameActivityGeneration(this.bootstrapRequest.generation, generation)) {
|
|
325
766
|
return this.bootstrapRequest.promise;
|
|
326
767
|
}
|
|
327
|
-
const promise = this.client.post("/api/v1/activity/session", this.buildSessionPayload(session)
|
|
328
|
-
|
|
768
|
+
const promise = this.client.post("/api/v1/activity/session", this.buildSessionPayload(session), {
|
|
769
|
+
retryPolicy: ACTIVITY_RETRY_POLICY
|
|
770
|
+
}).then(() => {
|
|
771
|
+
if (this.isCurrentGeneration(generation)) {
|
|
772
|
+
this.sessions.markBootstrapped(session.id);
|
|
773
|
+
}
|
|
329
774
|
}).finally(() => {
|
|
330
775
|
if (this.bootstrapRequest?.promise === promise) {
|
|
331
776
|
this.bootstrapRequest = null;
|
|
332
777
|
}
|
|
333
778
|
});
|
|
334
|
-
this.bootstrapRequest = { sessionId: session.id, promise };
|
|
779
|
+
this.bootstrapRequest = { sessionId: session.id, generation, promise };
|
|
335
780
|
return promise;
|
|
336
781
|
}
|
|
782
|
+
captureGeneration() {
|
|
783
|
+
return {
|
|
784
|
+
local: this.localGeneration,
|
|
785
|
+
publicKey: this.client.getPublicKeyEpoch(),
|
|
786
|
+
session: this.client.getSessionEpoch()
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
isCurrentGeneration(generation) {
|
|
790
|
+
return sameActivityGeneration(generation, this.captureGeneration());
|
|
791
|
+
}
|
|
792
|
+
invalidatePendingActivity() {
|
|
793
|
+
this.localGeneration += 1;
|
|
794
|
+
this.bootstrapRequest = null;
|
|
795
|
+
}
|
|
796
|
+
createSessionCoordinator() {
|
|
797
|
+
return new AnalyticsSessionCoordinator(pageViewTrackerIdentity(this.client));
|
|
798
|
+
}
|
|
337
799
|
};
|
|
800
|
+
function sameActivityGeneration(left, right) {
|
|
801
|
+
return left.local === right.local && left.publicKey === right.publicKey && left.session === right.session;
|
|
802
|
+
}
|
|
338
803
|
function currentPath() {
|
|
339
804
|
if (typeof window !== "undefined") {
|
|
340
805
|
return window.location.pathname;
|
|
@@ -414,14 +879,14 @@ var UploadService = class {
|
|
|
414
879
|
constructor(client) {
|
|
415
880
|
this.client = client;
|
|
416
881
|
}
|
|
417
|
-
async init(filename,
|
|
882
|
+
async init(filename, contentType2, sizeBytes, opts) {
|
|
418
883
|
const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
|
|
419
884
|
return safe(
|
|
420
885
|
this.client.post(
|
|
421
886
|
"/api/v1/uploads/init",
|
|
422
887
|
{
|
|
423
888
|
filename,
|
|
424
|
-
content_type:
|
|
889
|
+
content_type: contentType2,
|
|
425
890
|
size_bytes: sizeBytes
|
|
426
891
|
},
|
|
427
892
|
{ idempotencyKey }
|
|
@@ -522,6 +987,99 @@ var DeliveryService = class {
|
|
|
522
987
|
}
|
|
523
988
|
};
|
|
524
989
|
|
|
990
|
+
// src/chat-wire.ts
|
|
991
|
+
function senderType(message) {
|
|
992
|
+
if (message.sender_side === "account") return "customer";
|
|
993
|
+
if (message.sender_side === "system") return "system";
|
|
994
|
+
return message.authorship === "ai" ? "bot" : "agent";
|
|
995
|
+
}
|
|
996
|
+
function contentType(message) {
|
|
997
|
+
if (message.content_type === "rich") {
|
|
998
|
+
return message.structured_content?.["kind"] === "reaction" ? "reaction" : "interactive";
|
|
999
|
+
}
|
|
1000
|
+
if (message.content_type === "media") {
|
|
1001
|
+
const mime = message.media[0]?.mime_type ?? "";
|
|
1002
|
+
if (mime.startsWith("image/")) return "image";
|
|
1003
|
+
if (mime.startsWith("video/")) return "video";
|
|
1004
|
+
if (mime.startsWith("audio/")) return "audio";
|
|
1005
|
+
return "document";
|
|
1006
|
+
}
|
|
1007
|
+
return "text";
|
|
1008
|
+
}
|
|
1009
|
+
function unresolvedReply(id) {
|
|
1010
|
+
return { id, sender_type: "system", preview: "Earlier message" };
|
|
1011
|
+
}
|
|
1012
|
+
function chatMessagesFromWire(messages) {
|
|
1013
|
+
const converted = messages.map((message) => ({
|
|
1014
|
+
id: message.id,
|
|
1015
|
+
sequence: message.sequence,
|
|
1016
|
+
sender_type: senderType(message),
|
|
1017
|
+
content: message.content,
|
|
1018
|
+
content_type: contentType(message),
|
|
1019
|
+
attachments: Array.isArray(message.media) ? message.media : [],
|
|
1020
|
+
metadata: message.structured_content ?? {},
|
|
1021
|
+
reply_to: message.reply_to_message_id ? unresolvedReply(message.reply_to_message_id) : void 0,
|
|
1022
|
+
edited_at: message.edited_at,
|
|
1023
|
+
deleted_at: message.deleted_at,
|
|
1024
|
+
created_at: message.created_at
|
|
1025
|
+
}));
|
|
1026
|
+
const byId = new Map(converted.map((message) => [message.id, message]));
|
|
1027
|
+
return converted.map((message) => {
|
|
1028
|
+
const targetId = message.reply_to?.id;
|
|
1029
|
+
const target = targetId ? byId.get(targetId) : void 0;
|
|
1030
|
+
if (!targetId || !target) return message;
|
|
1031
|
+
return {
|
|
1032
|
+
...message,
|
|
1033
|
+
reply_to: {
|
|
1034
|
+
id: targetId,
|
|
1035
|
+
sender_type: target.sender_type,
|
|
1036
|
+
preview: messagePreview(target.content)
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
function chatMessageFromWire(message) {
|
|
1042
|
+
return chatMessagesFromWire([message])[0];
|
|
1043
|
+
}
|
|
1044
|
+
function chatRealtimeUpdateFromWire(envelope) {
|
|
1045
|
+
if (envelope.type === "message_created" && envelope.data) {
|
|
1046
|
+
return {
|
|
1047
|
+
kind: "message",
|
|
1048
|
+
message: chatMessageFromWire(envelope.data)
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
if (envelope.type === "message_updated" && envelope.data) {
|
|
1052
|
+
return {
|
|
1053
|
+
kind: "message_updated",
|
|
1054
|
+
message: chatMessageFromWire(envelope.data)
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
if (envelope.type === "read" && envelope.data) {
|
|
1058
|
+
const read = envelope.data;
|
|
1059
|
+
if (read.reader_side === "business" && typeof read.occurred_at === "string") {
|
|
1060
|
+
return { kind: "business_read", occurredAt: read.occurred_at };
|
|
1061
|
+
}
|
|
1062
|
+
return { kind: "none" };
|
|
1063
|
+
}
|
|
1064
|
+
if (envelope.type === "history_imported") {
|
|
1065
|
+
return { kind: "reload" };
|
|
1066
|
+
}
|
|
1067
|
+
if (envelope.type === "ai_mode_changed" && envelope.data) {
|
|
1068
|
+
const mode = envelope.data.mode;
|
|
1069
|
+
if (mode === "auto" || mode === "assist" || mode === "off") {
|
|
1070
|
+
return { kind: "ai_mode", mode };
|
|
1071
|
+
}
|
|
1072
|
+
return { kind: "none" };
|
|
1073
|
+
}
|
|
1074
|
+
if (envelope.type === "handoff_requested") {
|
|
1075
|
+
return { kind: "handoff" };
|
|
1076
|
+
}
|
|
1077
|
+
if (envelope.type === "resync") {
|
|
1078
|
+
return { kind: "resync", latestSequence: envelope.latest_sequence };
|
|
1079
|
+
}
|
|
1080
|
+
return { kind: "none" };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
525
1083
|
// src/internal/events.ts
|
|
526
1084
|
var MUTATION_ERROR = "mutation:error";
|
|
527
1085
|
var AUTH_TOKEN_CHANGED = "auth:token-changed";
|
|
@@ -584,11 +1142,13 @@ var SupportService = class {
|
|
|
584
1142
|
* Claim the anonymous session for the verified Link identity: the server
|
|
585
1143
|
* verifies the access token, rotates the session token (adopted here, so
|
|
586
1144
|
* every subsequent request rides the bound session), folds the guest cart
|
|
587
|
-
* in, and
|
|
1145
|
+
* in, and opens or reuses the account's canonical direct Chat thread.
|
|
588
1146
|
*
|
|
589
1147
|
* Idempotent and single-flight: concurrent calls share one request, and a
|
|
590
1148
|
* token already claimed by this instance short-circuits without a network
|
|
591
|
-
* round trip.
|
|
1149
|
+
* round trip. A session already bound to another Link account is rejected
|
|
1150
|
+
* rather than transferring that account's cart. Emits `session:claimed` on
|
|
1151
|
+
* the client's event channel.
|
|
592
1152
|
*/
|
|
593
1153
|
async claimConversation() {
|
|
594
1154
|
const accessToken = this.client.getAccessToken();
|
|
@@ -625,7 +1185,7 @@ var SupportService = class {
|
|
|
625
1185
|
this.client.publishSessionClaimed(this.claimedName);
|
|
626
1186
|
this.client.events.emit(SESSION_CLAIMED, {
|
|
627
1187
|
sessionToken: result.value.session_token,
|
|
628
|
-
threadId: result.value.
|
|
1188
|
+
threadId: result.value.thread.id,
|
|
629
1189
|
customerName: result.value.customer_name
|
|
630
1190
|
});
|
|
631
1191
|
}
|
|
@@ -638,6 +1198,25 @@ var SupportService = class {
|
|
|
638
1198
|
this.claimInFlightToken = null;
|
|
639
1199
|
}
|
|
640
1200
|
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Retry verified external-history discovery for the current storefront.
|
|
1203
|
+
* Launch scope is intentionally WhatsApp-only because it is the registered
|
|
1204
|
+
* bidirectional transport. Results are per envelope and omit provider ids,
|
|
1205
|
+
* contact values, account ids, and message bodies.
|
|
1206
|
+
*/
|
|
1207
|
+
async claimExternalChats() {
|
|
1208
|
+
if (!this.client.getAccessToken()) {
|
|
1209
|
+
return err(new CimplifyError("not_authenticated", "No access token to claim with"));
|
|
1210
|
+
}
|
|
1211
|
+
return safe(
|
|
1212
|
+
(async () => {
|
|
1213
|
+
const businessId = await this.client.resolveBusinessId();
|
|
1214
|
+
return this.client.post("/v1/me/chats/external-claims", {
|
|
1215
|
+
business_id: businessId
|
|
1216
|
+
});
|
|
1217
|
+
})()
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
641
1220
|
/**
|
|
642
1221
|
* Peek at the conversation without creating one: resolves the existing
|
|
643
1222
|
* thread (or null) so a page load can hydrate history without littering
|
|
@@ -651,17 +1230,22 @@ var SupportService = class {
|
|
|
651
1230
|
if (!result.value.conversation) return ok(null);
|
|
652
1231
|
return ok({
|
|
653
1232
|
conversation: result.value.conversation,
|
|
654
|
-
messages: result.value.messages
|
|
1233
|
+
messages: chatMessagesFromWire(result.value.messages)
|
|
655
1234
|
});
|
|
656
1235
|
}
|
|
657
1236
|
/** Open (or resume) the customer's widget conversation. */
|
|
658
1237
|
async openConversation(opts) {
|
|
659
1238
|
const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
|
|
660
|
-
|
|
1239
|
+
const result = await safe(
|
|
661
1240
|
this.client.post("/api/v1/support/conversation", void 0, {
|
|
662
1241
|
idempotencyKey
|
|
663
1242
|
})
|
|
664
1243
|
);
|
|
1244
|
+
if (!result.ok) return result;
|
|
1245
|
+
return ok({
|
|
1246
|
+
conversation: result.value.conversation,
|
|
1247
|
+
messages: chatMessagesFromWire(result.value.messages)
|
|
1248
|
+
});
|
|
665
1249
|
}
|
|
666
1250
|
/**
|
|
667
1251
|
* Send a message in the active conversation.
|
|
@@ -673,7 +1257,7 @@ var SupportService = class {
|
|
|
673
1257
|
*/
|
|
674
1258
|
async sendMessage(content, opts) {
|
|
675
1259
|
const idempotencyKey = opts?.idempotencyKey ?? generateIdempotencyKey();
|
|
676
|
-
|
|
1260
|
+
const result = await safe(
|
|
677
1261
|
this.client.post(
|
|
678
1262
|
"/api/v1/support/conversation/messages",
|
|
679
1263
|
{
|
|
@@ -685,6 +1269,7 @@ var SupportService = class {
|
|
|
685
1269
|
{ idempotencyKey }
|
|
686
1270
|
)
|
|
687
1271
|
);
|
|
1272
|
+
return result.ok ? ok(chatMessageFromWire(result.value.message)) : result;
|
|
688
1273
|
}
|
|
689
1274
|
/**
|
|
690
1275
|
* Poll for messages. `after` pages forward (live updates); `before` pages
|
|
@@ -692,21 +1277,27 @@ var SupportService = class {
|
|
|
692
1277
|
*/
|
|
693
1278
|
async getMessages(options = {}) {
|
|
694
1279
|
const params = new URLSearchParams();
|
|
695
|
-
if (options.
|
|
696
|
-
|
|
1280
|
+
if (options.afterSequence !== void 0)
|
|
1281
|
+
params.set("after_sequence", String(options.afterSequence));
|
|
1282
|
+
if (options.beforeSequence !== void 0)
|
|
1283
|
+
params.set("before_sequence", String(options.beforeSequence));
|
|
697
1284
|
if (options.limit) params.set("limit", String(options.limit));
|
|
698
1285
|
const query = params.toString();
|
|
699
1286
|
const path = query ? `/api/v1/support/conversation/messages?${query}` : "/api/v1/support/conversation/messages";
|
|
700
|
-
|
|
1287
|
+
const result = await safe(this.client.get(path));
|
|
1288
|
+
return result.ok ? ok(chatMessagesFromWire(result.value)) : result;
|
|
701
1289
|
}
|
|
702
1290
|
/** React to a message in the active conversation by its `ChatMessage.id`. */
|
|
703
1291
|
async reactToMessage(messageId, emoji) {
|
|
704
|
-
|
|
1292
|
+
const clientId = generateIdempotencyKey();
|
|
1293
|
+
const result = await safe(
|
|
705
1294
|
this.client.post(
|
|
706
1295
|
`/api/v1/support/conversation/messages/${messageId}/reactions`,
|
|
707
|
-
{ emoji }
|
|
1296
|
+
{ emoji, client_id: clientId },
|
|
1297
|
+
{ idempotencyKey: clientId }
|
|
708
1298
|
)
|
|
709
1299
|
);
|
|
1300
|
+
return result.ok ? ok(chatMessageFromWire(result.value.message)) : result;
|
|
710
1301
|
}
|
|
711
1302
|
/** Ask for a person: flags the thread for the merchant's inbox. */
|
|
712
1303
|
async requestHuman(opts) {
|
|
@@ -721,456 +1312,108 @@ var SupportService = class {
|
|
|
721
1312
|
}
|
|
722
1313
|
};
|
|
723
1314
|
|
|
724
|
-
// src/
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
try {
|
|
731
|
-
const imported = await import('@paystack/inline-js');
|
|
732
|
-
PaystackPop = imported.default;
|
|
733
|
-
} catch {
|
|
734
|
-
return { success: false, error: "PROVIDER_UNAVAILABLE" };
|
|
735
|
-
}
|
|
736
|
-
return new Promise((resolve) => {
|
|
737
|
-
let settled = false;
|
|
738
|
-
let providerStarted = false;
|
|
739
|
-
const resolveOnce = (result) => {
|
|
740
|
-
if (settled) {
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
settled = true;
|
|
744
|
-
if (signal) {
|
|
745
|
-
signal.removeEventListener("abort", onAbort);
|
|
746
|
-
}
|
|
747
|
-
resolve(result);
|
|
748
|
-
};
|
|
749
|
-
const onAbort = () => {
|
|
750
|
-
resolveOnce({
|
|
751
|
-
success: false,
|
|
752
|
-
// Once Paystack owns the transaction, cancellation is ambiguous. The
|
|
753
|
-
// adapter converts this into submitted so Cimplify polling decides.
|
|
754
|
-
error: providerStarted ? "SUBMISSION_UNKNOWN" : "CANCELLED"
|
|
755
|
-
});
|
|
756
|
-
};
|
|
757
|
-
if (signal?.aborted) {
|
|
758
|
-
resolveOnce({ success: false, error: "CANCELLED" });
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
if (signal) {
|
|
762
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
763
|
-
}
|
|
764
|
-
try {
|
|
765
|
-
const popup = new PaystackPop();
|
|
766
|
-
popup.newTransaction({
|
|
767
|
-
key: options.key,
|
|
768
|
-
email: options.email,
|
|
769
|
-
amount: options.amount,
|
|
770
|
-
currency: options.currency,
|
|
771
|
-
reference: options.reference,
|
|
772
|
-
accessCode: options.accessCode,
|
|
773
|
-
onSuccess: (transaction) => {
|
|
774
|
-
resolveOnce({
|
|
775
|
-
success: true,
|
|
776
|
-
reference: transaction.reference ?? options.reference
|
|
777
|
-
});
|
|
778
|
-
},
|
|
779
|
-
onCancel: () => {
|
|
780
|
-
resolveOnce({ success: false, error: "PAYMENT_CANCELLED" });
|
|
781
|
-
},
|
|
782
|
-
onError: (error) => {
|
|
783
|
-
resolveOnce({
|
|
784
|
-
success: false,
|
|
785
|
-
error: error?.message || "PAYMENT_FAILED"
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
});
|
|
789
|
-
providerStarted = true;
|
|
790
|
-
} catch {
|
|
791
|
-
resolveOnce({ success: false, error: "POPUP_BLOCKED" });
|
|
792
|
-
}
|
|
793
|
-
});
|
|
1315
|
+
// src/internal/cross-tab-session.ts
|
|
1316
|
+
var CROSS_TAB_SESSION_HUB_KEY = /* @__PURE__ */ Symbol.for("@cimplify/sdk.cross-tab-session-hub");
|
|
1317
|
+
var SESSION_SYNC_STORAGE_KEY = "cimplify_session_sync";
|
|
1318
|
+
var MAX_RECENT_ENVELOPES = 64;
|
|
1319
|
+
function hubHost() {
|
|
1320
|
+
return window;
|
|
794
1321
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
currency: context.currency
|
|
808
|
-
},
|
|
809
|
-
context.signal
|
|
810
|
-
);
|
|
811
|
-
if (result.success) {
|
|
812
|
-
return { status: "submitted", reference: result.reference || context.reference };
|
|
813
|
-
}
|
|
814
|
-
if (result.error === "SUBMISSION_UNKNOWN") {
|
|
815
|
-
return { status: "submitted", reference: context.reference };
|
|
816
|
-
}
|
|
817
|
-
if (!result.error || result.error === "CANCELLED" || result.error === "PAYMENT_CANCELLED") {
|
|
818
|
-
return { status: "cancelled" };
|
|
819
|
-
}
|
|
820
|
-
if (result.error === "POPUP_BLOCKED") {
|
|
821
|
-
return {
|
|
822
|
-
status: "failed",
|
|
823
|
-
code: "POPUP_BLOCKED",
|
|
824
|
-
message: "Unable to open the Paystack payment popup. Please allow popups and try again.",
|
|
825
|
-
recoverable: true
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
if (result.error === "PROVIDER_UNAVAILABLE") {
|
|
829
|
-
return {
|
|
830
|
-
status: "failed",
|
|
831
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
832
|
-
message: "Paystack is unavailable in this environment.",
|
|
833
|
-
recoverable: false
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
return {
|
|
837
|
-
status: "failed",
|
|
838
|
-
code: "PAYMENT_FAILED",
|
|
839
|
-
message: result.error || "Paystack payment could not be submitted.",
|
|
840
|
-
recoverable: true
|
|
841
|
-
};
|
|
1322
|
+
function currentHub() {
|
|
1323
|
+
const candidate = hubHost()[CROSS_TAB_SESSION_HUB_KEY];
|
|
1324
|
+
return candidate?.subscribers instanceof Map && candidate.recentEnvelopes instanceof Set && typeof candidate.storageListener === "function" && typeof candidate.dispatch === "function" ? candidate : null;
|
|
1325
|
+
}
|
|
1326
|
+
function subscriberReference(target) {
|
|
1327
|
+
const WeakReference = globalThis.WeakRef;
|
|
1328
|
+
if (WeakReference) return new WeakReference(target);
|
|
1329
|
+
return { deref: () => target };
|
|
1330
|
+
}
|
|
1331
|
+
function pruneSubscribers(hub) {
|
|
1332
|
+
for (const [token, reference] of hub.subscribers) {
|
|
1333
|
+
if (!reference.deref()) hub.subscribers.delete(token);
|
|
842
1334
|
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
async function loadStripeFromPeer(publicKey) {
|
|
1335
|
+
}
|
|
1336
|
+
function parseSessionSyncPayload(serialized) {
|
|
1337
|
+
if (!serialized) return null;
|
|
847
1338
|
try {
|
|
848
|
-
const
|
|
849
|
-
return
|
|
1339
|
+
const payload = JSON.parse(serialized);
|
|
1340
|
+
return (payload.type === "claimed" || payload.type === "cleared") && typeof payload.token === "string" && payload.token.length > 0 && typeof payload.at === "number" && Number.isFinite(payload.at) && (payload.name === void 0 || payload.name === null || typeof payload.name === "string") ? payload : null;
|
|
850
1341
|
} catch {
|
|
851
1342
|
return null;
|
|
852
1343
|
}
|
|
853
1344
|
}
|
|
854
|
-
function
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
const root = document.createElement("div");
|
|
858
|
-
root.setAttribute("role", "dialog");
|
|
859
|
-
root.setAttribute("aria-modal", "true");
|
|
860
|
-
root.setAttribute("aria-label", "Secure card payment");
|
|
861
|
-
Object.assign(root.style, {
|
|
862
|
-
position: "fixed",
|
|
863
|
-
inset: "0",
|
|
864
|
-
zIndex: "2147483647",
|
|
865
|
-
display: "grid",
|
|
866
|
-
placeItems: "center",
|
|
867
|
-
padding: "16px",
|
|
868
|
-
background: "rgba(15, 23, 42, 0.58)"
|
|
869
|
-
});
|
|
870
|
-
const form = document.createElement("form");
|
|
871
|
-
Object.assign(form.style, {
|
|
872
|
-
width: "min(100%, 480px)",
|
|
873
|
-
maxHeight: "calc(100vh - 32px)",
|
|
874
|
-
overflow: "auto",
|
|
875
|
-
borderRadius: "12px",
|
|
876
|
-
padding: "24px",
|
|
877
|
-
background: "#fff",
|
|
878
|
-
boxShadow: "0 24px 80px rgba(15, 23, 42, 0.32)"
|
|
879
|
-
});
|
|
880
|
-
const title = document.createElement("h2");
|
|
881
|
-
title.textContent = "Complete card payment";
|
|
882
|
-
Object.assign(title.style, { margin: "0 0 18px", font: "600 18px/1.4 system-ui" });
|
|
883
|
-
const paymentMount = document.createElement("div");
|
|
884
|
-
paymentMount.setAttribute("data-cimplify-stripe-payment-element", "");
|
|
885
|
-
const error = document.createElement("p");
|
|
886
|
-
error.setAttribute("role", "alert");
|
|
887
|
-
Object.assign(error.style, {
|
|
888
|
-
minHeight: "20px",
|
|
889
|
-
margin: "14px 0 0",
|
|
890
|
-
color: "#b91c1c",
|
|
891
|
-
font: "14px/1.4 system-ui"
|
|
892
|
-
});
|
|
893
|
-
const actions = document.createElement("div");
|
|
894
|
-
Object.assign(actions.style, {
|
|
895
|
-
display: "flex",
|
|
896
|
-
justifyContent: "flex-end",
|
|
897
|
-
gap: "10px",
|
|
898
|
-
marginTop: "18px"
|
|
899
|
-
});
|
|
900
|
-
const cancelButton = document.createElement("button");
|
|
901
|
-
cancelButton.type = "button";
|
|
902
|
-
cancelButton.textContent = "Cancel";
|
|
903
|
-
Object.assign(cancelButton.style, {
|
|
904
|
-
border: "1px solid #cbd5e1",
|
|
905
|
-
borderRadius: "8px",
|
|
906
|
-
padding: "10px 16px",
|
|
907
|
-
background: "#fff",
|
|
908
|
-
color: "#0f172a",
|
|
909
|
-
cursor: "pointer"
|
|
910
|
-
});
|
|
911
|
-
const submitButton = document.createElement("button");
|
|
912
|
-
submitButton.type = "submit";
|
|
913
|
-
submitButton.textContent = "Pay securely";
|
|
914
|
-
Object.assign(submitButton.style, {
|
|
915
|
-
border: "0",
|
|
916
|
-
borderRadius: "8px",
|
|
917
|
-
padding: "10px 16px",
|
|
918
|
-
background: "#0f172a",
|
|
919
|
-
color: "#fff",
|
|
920
|
-
cursor: "pointer"
|
|
921
|
-
});
|
|
922
|
-
actions.append(cancelButton, submitButton);
|
|
923
|
-
form.append(title, paymentMount, error, actions);
|
|
924
|
-
root.append(form);
|
|
925
|
-
document.body.append(root);
|
|
926
|
-
cancelButton.focus();
|
|
927
|
-
return { root, form, paymentMount, submitButton, cancelButton, error, previousFocus };
|
|
1345
|
+
function sessionSyncEnvelopeKey(payload) {
|
|
1346
|
+
const name = payload.name === void 0 ? ["missing"] : ["present", payload.name];
|
|
1347
|
+
return JSON.stringify([payload.type, payload.token, name, payload.at]);
|
|
928
1348
|
}
|
|
929
|
-
function
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
return { status: "failed", code: "PAYMENT_FAILED", message, recoverable: true };
|
|
1349
|
+
function rememberEnvelope(hub, envelope) {
|
|
1350
|
+
if (hub.recentEnvelopes.has(envelope)) return false;
|
|
1351
|
+
hub.recentEnvelopes.add(envelope);
|
|
1352
|
+
if (hub.recentEnvelopes.size > MAX_RECENT_ENVELOPES) {
|
|
1353
|
+
const oldest = hub.recentEnvelopes.values().next().value;
|
|
1354
|
+
if (oldest) hub.recentEnvelopes.delete(oldest);
|
|
1355
|
+
}
|
|
1356
|
+
return true;
|
|
940
1357
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
if (
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
} catch (error) {
|
|
959
|
-
return {
|
|
960
|
-
status: "failed",
|
|
961
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
962
|
-
message: error instanceof Error ? error.message : "Stripe.js could not be loaded.",
|
|
963
|
-
recoverable: true
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
if (context.signal?.aborted) {
|
|
967
|
-
return { status: "cancelled" };
|
|
968
|
-
}
|
|
969
|
-
if (!stripe) {
|
|
970
|
-
return {
|
|
971
|
-
status: "failed",
|
|
972
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
973
|
-
message: "Stripe.js could not be loaded.",
|
|
974
|
-
recoverable: false
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
const modal = createStripeModal();
|
|
978
|
-
if (!modal) {
|
|
979
|
-
return {
|
|
980
|
-
status: "failed",
|
|
981
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
982
|
-
message: "Stripe Payment Element could not be mounted.",
|
|
983
|
-
recoverable: false
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
let paymentElement;
|
|
987
|
-
let elements;
|
|
988
|
-
try {
|
|
989
|
-
elements = stripe.elements({ clientSecret: context.action.client_secret });
|
|
990
|
-
paymentElement = elements.create("payment");
|
|
991
|
-
paymentElement.mount(modal.paymentMount);
|
|
992
|
-
} catch {
|
|
993
|
-
modal.root.remove();
|
|
994
|
-
modal.previousFocus?.focus();
|
|
995
|
-
return {
|
|
996
|
-
status: "failed",
|
|
997
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
998
|
-
message: "Stripe Payment Element could not be initialized.",
|
|
999
|
-
recoverable: false
|
|
1000
|
-
};
|
|
1358
|
+
function createHub() {
|
|
1359
|
+
const hub = {
|
|
1360
|
+
subscribers: /* @__PURE__ */ new Map(),
|
|
1361
|
+
recentEnvelopes: /* @__PURE__ */ new Set()
|
|
1362
|
+
};
|
|
1363
|
+
hub.dispatch = (payload) => {
|
|
1364
|
+
if (!rememberEnvelope(hub, sessionSyncEnvelopeKey(payload))) return;
|
|
1365
|
+
for (const [token, reference] of hub.subscribers) {
|
|
1366
|
+
const subscriber = reference.deref();
|
|
1367
|
+
if (!subscriber) {
|
|
1368
|
+
hub.subscribers.delete(token);
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
try {
|
|
1372
|
+
subscriber.notify(payload);
|
|
1373
|
+
} catch {
|
|
1374
|
+
}
|
|
1001
1375
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
let confirmationStarted = false;
|
|
1013
|
-
const cleanup = () => {
|
|
1014
|
-
context.signal?.removeEventListener("abort", onAbort);
|
|
1015
|
-
modal.form.removeEventListener("submit", onSubmit);
|
|
1016
|
-
modal.cancelButton.removeEventListener("click", onCancel);
|
|
1017
|
-
if (paymentElement.destroy) paymentElement.destroy();
|
|
1018
|
-
else paymentElement.unmount?.();
|
|
1019
|
-
modal.root.remove();
|
|
1020
|
-
modal.previousFocus?.focus();
|
|
1021
|
-
};
|
|
1022
|
-
const settle = (outcome) => {
|
|
1023
|
-
if (settled) return;
|
|
1024
|
-
settled = true;
|
|
1025
|
-
cleanup();
|
|
1026
|
-
resolve(outcome);
|
|
1027
|
-
};
|
|
1028
|
-
const onAbort = () => {
|
|
1029
|
-
if (confirmationStarted) {
|
|
1030
|
-
settle({ status: "submitted", reference: context.reference });
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
settle({ status: "cancelled" });
|
|
1034
|
-
};
|
|
1035
|
-
const onCancel = () => {
|
|
1036
|
-
if (!confirmationStarted) settle({ status: "cancelled" });
|
|
1037
|
-
};
|
|
1038
|
-
const onSubmit = async (event) => {
|
|
1039
|
-
event.preventDefault();
|
|
1040
|
-
if (submitting || settled) return;
|
|
1041
|
-
submitting = true;
|
|
1042
|
-
modal.submitButton.disabled = true;
|
|
1043
|
-
modal.error.textContent = "";
|
|
1044
|
-
try {
|
|
1045
|
-
const validation = elements.submit ? await elements.submit() : {};
|
|
1046
|
-
if (validation.error) {
|
|
1047
|
-
modal.error.textContent = validation.error.message || "Check your payment details.";
|
|
1048
|
-
submitting = false;
|
|
1049
|
-
modal.submitButton.disabled = false;
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
if (settled || context.signal?.aborted) {
|
|
1053
|
-
settle({ status: "cancelled" });
|
|
1054
|
-
return;
|
|
1055
|
-
}
|
|
1056
|
-
confirmationStarted = true;
|
|
1057
|
-
modal.cancelButton.disabled = true;
|
|
1058
|
-
const confirmation = await stripe.confirmPayment({
|
|
1059
|
-
elements,
|
|
1060
|
-
confirmParams: { return_url: context.returnUrl || window.location.href },
|
|
1061
|
-
redirect: "if_required"
|
|
1062
|
-
});
|
|
1063
|
-
if (settled) return;
|
|
1064
|
-
if (confirmation.error) {
|
|
1065
|
-
settle(stripeFailure(confirmation.error));
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
settle({
|
|
1069
|
-
status: "submitted",
|
|
1070
|
-
reference: confirmation.paymentIntent?.id || context.reference
|
|
1071
|
-
});
|
|
1072
|
-
} catch (error) {
|
|
1073
|
-
if (confirmationStarted) {
|
|
1074
|
-
settle({ status: "submitted", reference: context.reference });
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
settle(
|
|
1078
|
-
stripeFailure({
|
|
1079
|
-
message: error instanceof Error ? error.message : "Stripe confirmation failed."
|
|
1080
|
-
})
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
};
|
|
1084
|
-
modal.form.addEventListener("submit", onSubmit);
|
|
1085
|
-
modal.cancelButton.addEventListener("click", onCancel);
|
|
1086
|
-
context.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1087
|
-
paymentElement.on?.("escape", onCancel);
|
|
1088
|
-
paymentElement.on?.("loaderror", (event) => {
|
|
1089
|
-
if (confirmationStarted) return;
|
|
1090
|
-
settle({
|
|
1091
|
-
status: "failed",
|
|
1092
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
1093
|
-
message: event?.error?.message || "Stripe Payment Element failed to load.",
|
|
1094
|
-
recoverable: true
|
|
1095
|
-
});
|
|
1096
|
-
});
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
|
-
};
|
|
1100
|
-
|
|
1101
|
-
// src/payment-authorization/registry.ts
|
|
1102
|
-
function normalizeProvider(provider) {
|
|
1103
|
-
return typeof provider === "string" ? provider.trim().toLowerCase() : "";
|
|
1376
|
+
};
|
|
1377
|
+
hub.storageListener = (event) => {
|
|
1378
|
+
const storageEvent = event;
|
|
1379
|
+
if (storageEvent.key !== SESSION_SYNC_STORAGE_KEY) return;
|
|
1380
|
+
const payload = parseSessionSyncPayload(storageEvent.newValue);
|
|
1381
|
+
if (payload) hub.dispatch(payload);
|
|
1382
|
+
};
|
|
1383
|
+
window.addEventListener("storage", hub.storageListener);
|
|
1384
|
+
hubHost()[CROSS_TAB_SESSION_HUB_KEY] = hub;
|
|
1385
|
+
return hub;
|
|
1104
1386
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const provider = normalizeProvider(adapter.provider);
|
|
1114
|
-
if (!provider) {
|
|
1115
|
-
throw new TypeError("Payment authorization adapter provider cannot be empty.");
|
|
1116
|
-
}
|
|
1117
|
-
this.adapters.set(provider, adapter);
|
|
1118
|
-
return this;
|
|
1119
|
-
}
|
|
1120
|
-
unregister(provider) {
|
|
1121
|
-
return this.adapters.delete(normalizeProvider(provider));
|
|
1122
|
-
}
|
|
1123
|
-
get(provider) {
|
|
1124
|
-
return this.adapters.get(normalizeProvider(provider));
|
|
1125
|
-
}
|
|
1126
|
-
supports(provider) {
|
|
1127
|
-
return this.adapters.has(normalizeProvider(provider));
|
|
1128
|
-
}
|
|
1129
|
-
providers() {
|
|
1130
|
-
return [...this.adapters.keys()];
|
|
1131
|
-
}
|
|
1132
|
-
async authorize(action, options) {
|
|
1133
|
-
const provider = normalizeProvider(action.provider);
|
|
1134
|
-
if (!provider) {
|
|
1135
|
-
return {
|
|
1136
|
-
status: "failed",
|
|
1137
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
1138
|
-
message: "Card authorization response did not identify a payment provider.",
|
|
1139
|
-
recoverable: false
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
const adapter = this.adapters.get(provider);
|
|
1143
|
-
if (!adapter) {
|
|
1144
|
-
return {
|
|
1145
|
-
status: "failed",
|
|
1146
|
-
code: "PROVIDER_UNAVAILABLE",
|
|
1147
|
-
message: `Card authorization provider '${provider}' is not supported by this SDK client.`,
|
|
1148
|
-
recoverable: false
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
try {
|
|
1152
|
-
const normalizedAction = action.provider === provider ? action : { ...action, provider };
|
|
1153
|
-
return await adapter.authorize({ ...options, action: normalizedAction });
|
|
1154
|
-
} catch (error) {
|
|
1155
|
-
return {
|
|
1156
|
-
status: "failed",
|
|
1157
|
-
code: "PAYMENT_FAILED",
|
|
1158
|
-
message: error instanceof Error ? error.message : `Card authorization provider '${provider}' failed unexpectedly.`,
|
|
1159
|
-
recoverable: true
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1387
|
+
function publishLocalSessionSync(payload) {
|
|
1388
|
+
if (typeof window === "undefined" || typeof window.addEventListener !== "function") return;
|
|
1389
|
+
(currentHub() ?? createHub()).dispatch(payload);
|
|
1390
|
+
}
|
|
1391
|
+
function subscribeCrossTabSession(notify) {
|
|
1392
|
+
if (typeof window === "undefined" || typeof window.addEventListener !== "function") {
|
|
1393
|
+
return () => {
|
|
1394
|
+
};
|
|
1162
1395
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1396
|
+
const hub = currentHub() ?? createHub();
|
|
1397
|
+
pruneSubscribers(hub);
|
|
1398
|
+
const token = /* @__PURE__ */ Symbol("cross-tab-session-subscriber");
|
|
1399
|
+
const subscriber = { notify };
|
|
1400
|
+
hub.subscribers.set(token, subscriberReference(subscriber));
|
|
1401
|
+
let unsubscribed = false;
|
|
1402
|
+
return () => {
|
|
1403
|
+
if (unsubscribed) return;
|
|
1404
|
+
unsubscribed = true;
|
|
1405
|
+
subscriber.notify = () => {
|
|
1406
|
+
};
|
|
1407
|
+
hub.subscribers.delete(token);
|
|
1408
|
+
pruneSubscribers(hub);
|
|
1409
|
+
if (hub.subscribers.size !== 0) return;
|
|
1410
|
+
window.removeEventListener("storage", hub.storageListener);
|
|
1411
|
+
if (currentHub() === hub) delete hubHost()[CROSS_TAB_SESSION_HUB_KEY];
|
|
1412
|
+
};
|
|
1169
1413
|
}
|
|
1170
1414
|
|
|
1171
1415
|
// src/client.ts
|
|
1172
1416
|
var SESSION_TOKEN_STORAGE_KEY = "cimplify_session_token";
|
|
1173
|
-
var SESSION_SYNC_STORAGE_KEY = "cimplify_session_sync";
|
|
1174
1417
|
var ORDER_TOKEN_PREFIX = "cimplify_ot_";
|
|
1175
1418
|
var SESSION_TOKEN_HEADER = "x-session-token";
|
|
1176
1419
|
var LOCATION_ID_QUERY_PARAMETER = "location_id";
|
|
@@ -1178,8 +1421,37 @@ var ORDER_TOKEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
|
1178
1421
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
1179
1422
|
var DEFAULT_MAX_RETRIES = 3;
|
|
1180
1423
|
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
1181
|
-
|
|
1182
|
-
|
|
1424
|
+
var MAX_RETRY_AFTER_MS = 3e4;
|
|
1425
|
+
function sleep(ms, signal) {
|
|
1426
|
+
if (signal?.aborted) {
|
|
1427
|
+
return Promise.reject(new CimplifyError(ErrorCode.CANCELLED, "Request was cancelled.", true));
|
|
1428
|
+
}
|
|
1429
|
+
return new Promise((resolve, reject) => {
|
|
1430
|
+
const timer = setTimeout(() => {
|
|
1431
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1432
|
+
resolve();
|
|
1433
|
+
}, ms);
|
|
1434
|
+
const onAbort = () => {
|
|
1435
|
+
clearTimeout(timer);
|
|
1436
|
+
reject(new CimplifyError(ErrorCode.CANCELLED, "Request was cancelled.", true));
|
|
1437
|
+
};
|
|
1438
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
function parseRetryAfterMs(value) {
|
|
1442
|
+
if (!value) return null;
|
|
1443
|
+
const seconds = Number(value);
|
|
1444
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
1445
|
+
return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS);
|
|
1446
|
+
}
|
|
1447
|
+
const retryAt = Date.parse(value);
|
|
1448
|
+
if (!Number.isFinite(retryAt)) return null;
|
|
1449
|
+
return Math.min(Math.max(0, retryAt - Date.now()), MAX_RETRY_AFTER_MS);
|
|
1450
|
+
}
|
|
1451
|
+
function withPositiveJitter(delayMs, jitterRatio = 0) {
|
|
1452
|
+
const boundedRatio = Math.min(Math.max(jitterRatio, 0), 1);
|
|
1453
|
+
if (boundedRatio === 0 || delayMs === 0) return delayMs;
|
|
1454
|
+
return Math.round(delayMs + delayMs * boundedRatio * Math.random());
|
|
1183
1455
|
}
|
|
1184
1456
|
function nextInit(cacheOptions) {
|
|
1185
1457
|
if (!cacheOptions) return {};
|
|
@@ -1293,6 +1565,9 @@ var CimplifyClient = class {
|
|
|
1293
1565
|
this.businessId = null;
|
|
1294
1566
|
this.businessIdResolvePromise = null;
|
|
1295
1567
|
this.publicKeyEpoch = 0;
|
|
1568
|
+
this.stopCrossTabSessionWatch = null;
|
|
1569
|
+
this.latestSessionSyncAt = Number.NEGATIVE_INFINITY;
|
|
1570
|
+
this.latestSessionSyncOrderKey = null;
|
|
1296
1571
|
this.inflightRequests = /* @__PURE__ */ new Map();
|
|
1297
1572
|
/**
|
|
1298
1573
|
* App-level event channel. Subscribe to surface mutation rollbacks, outbox
|
|
@@ -1314,7 +1589,11 @@ var CimplifyClient = class {
|
|
|
1314
1589
|
this.authorizationAdapters = config.authorizationAdapterRegistry ?? createDefaultPaymentAuthorizationAdapterRegistry();
|
|
1315
1590
|
this.fetchImpl = (config.fetch ?? globalThis.fetch).bind(globalThis);
|
|
1316
1591
|
this.sessionToken = this.loadSessionToken() ?? this.mintSessionToken();
|
|
1317
|
-
this.
|
|
1592
|
+
const persistedSessionSync = this.loadSessionSyncPayload();
|
|
1593
|
+
if (persistedSessionSync) this.observeSessionSync(persistedSessionSync);
|
|
1594
|
+
this.stopCrossTabSessionWatch = subscribeCrossTabSession((payload) => {
|
|
1595
|
+
this.applyCrossTabSession(payload);
|
|
1596
|
+
});
|
|
1318
1597
|
if (!this.publicKey && !config.suppressPublicKeyWarning) {
|
|
1319
1598
|
console.warn(
|
|
1320
1599
|
'[Cimplify] No public key found. Set NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY in your environment, or pass { publicKey: "cpk_..." } to createCimplifyClient().'
|
|
@@ -1341,6 +1620,12 @@ var CimplifyClient = class {
|
|
|
1341
1620
|
this.stopPageViewTracking?.();
|
|
1342
1621
|
this.stopPageViewTracking = null;
|
|
1343
1622
|
}
|
|
1623
|
+
/** Release window-level subscriptions and automatic tracking owned by this client. */
|
|
1624
|
+
destroy() {
|
|
1625
|
+
this.stopTrackingPageViews();
|
|
1626
|
+
this.stopCrossTabSessionWatch?.();
|
|
1627
|
+
this.stopCrossTabSessionWatch = null;
|
|
1628
|
+
}
|
|
1344
1629
|
getAccessToken() {
|
|
1345
1630
|
return this.accessToken;
|
|
1346
1631
|
}
|
|
@@ -1368,7 +1653,7 @@ var CimplifyClient = class {
|
|
|
1368
1653
|
this.hooks.onSessionChange?.({
|
|
1369
1654
|
previousToken: previous,
|
|
1370
1655
|
newToken: token,
|
|
1371
|
-
source:
|
|
1656
|
+
source: SESSION_CHANGE_SOURCE.MANUAL
|
|
1372
1657
|
});
|
|
1373
1658
|
this.events.emit(AUTH_TOKEN_CHANGED, { token });
|
|
1374
1659
|
}
|
|
@@ -1377,7 +1662,8 @@ var CimplifyClient = class {
|
|
|
1377
1662
|
* refresh restore) binds the anonymous session so the conversation, cart
|
|
1378
1663
|
* and orders survive a cleared browser. Best-effort by design — a failed
|
|
1379
1664
|
* claim leaves the session anonymous, exactly as it was. Called by the
|
|
1380
|
-
* auth flows, never by the raw token setter.
|
|
1665
|
+
* merchant auth flows, never by the raw token setter. Global Link clients
|
|
1666
|
+
* have no storefront tenant and therefore activate without this step.
|
|
1381
1667
|
*/
|
|
1382
1668
|
autoClaimChatIdentity() {
|
|
1383
1669
|
if (typeof window === "undefined" || !this.sessionToken || !this.accessToken) return;
|
|
@@ -1411,7 +1697,7 @@ var CimplifyClient = class {
|
|
|
1411
1697
|
this.hooks.onSessionChange?.({
|
|
1412
1698
|
previousToken: previous,
|
|
1413
1699
|
newToken: null,
|
|
1414
|
-
source:
|
|
1700
|
+
source: SESSION_CHANGE_SOURCE.CLEAR
|
|
1415
1701
|
});
|
|
1416
1702
|
this.events.emit(AUTH_TOKEN_CHANGED, { token: null });
|
|
1417
1703
|
this.events.emit(SESSION_CLEARED, {});
|
|
@@ -1419,9 +1705,29 @@ var CimplifyClient = class {
|
|
|
1419
1705
|
getSessionEpoch() {
|
|
1420
1706
|
return this.sessionEpoch;
|
|
1421
1707
|
}
|
|
1708
|
+
/** Monotonic fence for async work scoped to the current storefront tenant. */
|
|
1709
|
+
getPublicKeyEpoch() {
|
|
1710
|
+
return this.publicKeyEpoch;
|
|
1711
|
+
}
|
|
1422
1712
|
getSessionToken() {
|
|
1423
1713
|
return this.sessionToken;
|
|
1424
1714
|
}
|
|
1715
|
+
/** Whether the current transport session is known to have been claimed for
|
|
1716
|
+
* a Link identity, even when its in-memory bearer is absent after reload or
|
|
1717
|
+
* a failed refresh. The sync marker is written only after canonical claim
|
|
1718
|
+
* rotation and is replaced with `cleared` when a fresh session is minted. */
|
|
1719
|
+
hasClaimedSessionAuthority() {
|
|
1720
|
+
if (!this.sessionToken) return false;
|
|
1721
|
+
try {
|
|
1722
|
+
const payload = parseSessionSyncPayload(
|
|
1723
|
+
this.safeStorage()?.getItem(SESSION_SYNC_STORAGE_KEY) ?? null
|
|
1724
|
+
);
|
|
1725
|
+
if (!payload) return false;
|
|
1726
|
+
return payload.type === "claimed" && payload.token === this.sessionToken;
|
|
1727
|
+
} catch {
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1425
1731
|
publishSessionClaimed(customerName) {
|
|
1426
1732
|
if (!this.sessionToken) return;
|
|
1427
1733
|
this.broadcastSessionSync({
|
|
@@ -1431,41 +1737,49 @@ var CimplifyClient = class {
|
|
|
1431
1737
|
});
|
|
1432
1738
|
}
|
|
1433
1739
|
broadcastSessionSync(payload) {
|
|
1740
|
+
const envelope = {
|
|
1741
|
+
...payload,
|
|
1742
|
+
at: this.nextSessionSyncTimestamp()
|
|
1743
|
+
};
|
|
1434
1744
|
try {
|
|
1435
|
-
this.safeStorage()?.setItem(
|
|
1436
|
-
SESSION_SYNC_STORAGE_KEY,
|
|
1437
|
-
JSON.stringify({ ...payload, at: Date.now() })
|
|
1438
|
-
);
|
|
1745
|
+
this.safeStorage()?.setItem(SESSION_SYNC_STORAGE_KEY, JSON.stringify(envelope));
|
|
1439
1746
|
} catch {
|
|
1440
1747
|
}
|
|
1748
|
+
publishLocalSessionSync(envelope);
|
|
1441
1749
|
}
|
|
1442
|
-
|
|
1443
|
-
if (
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1750
|
+
applyCrossTabSession(payload) {
|
|
1751
|
+
if (!this.observeSessionSync(payload)) return;
|
|
1752
|
+
if (payload.token === this.sessionToken) return;
|
|
1753
|
+
if (payload.type === "claimed") {
|
|
1754
|
+
this.sessionToken = payload.token;
|
|
1755
|
+
this.sessionEpoch++;
|
|
1756
|
+
this.events.emit(SESSION_CLAIMED, {
|
|
1757
|
+
sessionToken: payload.token,
|
|
1758
|
+
threadId: null,
|
|
1759
|
+
customerName: payload.name ?? ""
|
|
1760
|
+
});
|
|
1761
|
+
} else {
|
|
1762
|
+
this.accessToken = null;
|
|
1763
|
+
this._activity?.resetSession();
|
|
1764
|
+
this.sessionToken = payload.token;
|
|
1765
|
+
this.sessionEpoch++;
|
|
1766
|
+
this.events.emit(AUTH_TOKEN_CHANGED, { token: null });
|
|
1767
|
+
this.events.emit(SESSION_CLEARED, {});
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
observeSessionSync(payload) {
|
|
1771
|
+
if (payload.at < this.latestSessionSyncAt) return false;
|
|
1772
|
+
const envelope = sessionSyncEnvelopeKey(payload);
|
|
1773
|
+
if (payload.at === this.latestSessionSyncAt && this.latestSessionSyncOrderKey !== null && envelope <= this.latestSessionSyncOrderKey) {
|
|
1774
|
+
return false;
|
|
1775
|
+
}
|
|
1776
|
+
this.latestSessionSyncAt = payload.at;
|
|
1777
|
+
this.latestSessionSyncOrderKey = envelope;
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
nextSessionSyncTimestamp() {
|
|
1781
|
+
const now = Date.now();
|
|
1782
|
+
return Number.isFinite(this.latestSessionSyncAt) ? Math.max(now, this.latestSessionSyncAt + 1) : now;
|
|
1469
1783
|
}
|
|
1470
1784
|
setOrderToken(orderId, token) {
|
|
1471
1785
|
try {
|
|
@@ -1529,17 +1843,24 @@ var CimplifyClient = class {
|
|
|
1529
1843
|
setPublicKey(publicKey) {
|
|
1530
1844
|
const normalized = publicKey.trim();
|
|
1531
1845
|
if (normalized === this.publicKey) return;
|
|
1846
|
+
const restartPageViewTracking = this.stopPageViewTracking !== null;
|
|
1847
|
+
if (restartPageViewTracking) {
|
|
1848
|
+
this.stopTrackingPageViews();
|
|
1849
|
+
}
|
|
1532
1850
|
this.publicKey = normalized;
|
|
1533
1851
|
this.publicKeyEpoch += 1;
|
|
1534
1852
|
this.businessId = null;
|
|
1535
1853
|
this.businessIdResolvePromise = null;
|
|
1536
1854
|
this.inflightRequests.clear();
|
|
1537
|
-
this._activity?.
|
|
1855
|
+
this._activity?.resetForStorefrontIdentityChange();
|
|
1538
1856
|
delete this.context.location_id;
|
|
1539
1857
|
if (this.accessToken !== null) {
|
|
1540
1858
|
this.setAccessToken(null);
|
|
1541
1859
|
}
|
|
1542
1860
|
this.events.emit(SESSION_CLEARED, {});
|
|
1861
|
+
if (restartPageViewTracking) {
|
|
1862
|
+
this.stopPageViewTracking = this.activity.autoTrackPageViews();
|
|
1863
|
+
}
|
|
1543
1864
|
}
|
|
1544
1865
|
getBusinessId() {
|
|
1545
1866
|
return this.businessId;
|
|
@@ -1580,8 +1901,8 @@ var CimplifyClient = class {
|
|
|
1580
1901
|
}
|
|
1581
1902
|
}
|
|
1582
1903
|
}
|
|
1583
|
-
async authedFetch(url, options, timeoutMs) {
|
|
1584
|
-
return this.resilientFetch(url, options, timeoutMs);
|
|
1904
|
+
async authedFetch(url, options, timeoutMs, retryPolicy, signal) {
|
|
1905
|
+
return this.resilientFetch(url, options, timeoutMs, retryPolicy, signal);
|
|
1585
1906
|
}
|
|
1586
1907
|
// Storage guards: even the `window.localStorage` property access throws in
|
|
1587
1908
|
// some sandboxed iframes and strict private-browsing modes. These run in the
|
|
@@ -1603,6 +1924,13 @@ var CimplifyClient = class {
|
|
|
1603
1924
|
return null;
|
|
1604
1925
|
}
|
|
1605
1926
|
}
|
|
1927
|
+
loadSessionSyncPayload() {
|
|
1928
|
+
try {
|
|
1929
|
+
return parseSessionSyncPayload(this.safeStorage()?.getItem(SESSION_SYNC_STORAGE_KEY) ?? null);
|
|
1930
|
+
} catch {
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1606
1934
|
/**
|
|
1607
1935
|
* Mint the session token client-side so the very first request already
|
|
1608
1936
|
* carries it. Without this, parallel first-load requests each get a
|
|
@@ -1658,7 +1986,7 @@ var CimplifyClient = class {
|
|
|
1658
1986
|
}
|
|
1659
1987
|
return headers;
|
|
1660
1988
|
}
|
|
1661
|
-
async resilientFetch(url, options, timeoutMs) {
|
|
1989
|
+
async resilientFetch(url, options, timeoutMs, retryPolicy, signal) {
|
|
1662
1990
|
const method = options.method || "GET";
|
|
1663
1991
|
const path = url.replace(this.baseUrl, "").replace(this.linkApiUrl, "");
|
|
1664
1992
|
const startTime = Date.now();
|
|
@@ -1672,15 +2000,31 @@ var CimplifyClient = class {
|
|
|
1672
2000
|
};
|
|
1673
2001
|
this.hooks.onRequestStart?.(context);
|
|
1674
2002
|
let lastError;
|
|
1675
|
-
|
|
2003
|
+
const configuredMaxRetries = Math.max(0, Math.floor(this.maxRetries));
|
|
2004
|
+
const maxRetries = Math.min(
|
|
2005
|
+
configuredMaxRetries,
|
|
2006
|
+
Math.max(0, Math.floor(retryPolicy?.maxRetries ?? configuredMaxRetries))
|
|
2007
|
+
);
|
|
2008
|
+
const jitterRatio = retryPolicy?.jitterRatio ?? 0;
|
|
2009
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1676
2010
|
try {
|
|
2011
|
+
if (signal?.aborted) {
|
|
2012
|
+
throw new CimplifyError(ErrorCode.CANCELLED, "Request was cancelled.", true);
|
|
2013
|
+
}
|
|
1677
2014
|
const controller = new AbortController();
|
|
1678
2015
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs ?? this.timeout);
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
2016
|
+
const onAbort = () => controller.abort();
|
|
2017
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2018
|
+
let response;
|
|
2019
|
+
try {
|
|
2020
|
+
response = await this.fetchImpl(url, {
|
|
2021
|
+
...options,
|
|
2022
|
+
signal: controller.signal
|
|
2023
|
+
});
|
|
2024
|
+
} finally {
|
|
2025
|
+
clearTimeout(timeoutId);
|
|
2026
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2027
|
+
}
|
|
1684
2028
|
this.captureSessionToken(response);
|
|
1685
2029
|
if (response.ok) {
|
|
1686
2030
|
this.hooks.onRequestSuccess?.({
|
|
@@ -1690,17 +2034,19 @@ var CimplifyClient = class {
|
|
|
1690
2034
|
});
|
|
1691
2035
|
return response;
|
|
1692
2036
|
}
|
|
1693
|
-
if ((response.status === 429 || response.status === 503) && attempt <
|
|
2037
|
+
if ((response.status === 429 || response.status === 503) && attempt < maxRetries) {
|
|
1694
2038
|
retryCount++;
|
|
1695
|
-
const
|
|
1696
|
-
const
|
|
2039
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("Retry-After"));
|
|
2040
|
+
const retryBaseMs = retryAfterMs ?? this.retryDelay * Math.pow(2, attempt);
|
|
2041
|
+
const jitteredDelayMs = withPositiveJitter(retryBaseMs, jitterRatio);
|
|
2042
|
+
const delay = retryAfterMs === null ? jitteredDelayMs : Math.min(jitteredDelayMs, MAX_RETRY_AFTER_MS);
|
|
1697
2043
|
this.hooks.onRetry?.({
|
|
1698
2044
|
...context,
|
|
1699
2045
|
attempt: retryCount,
|
|
1700
2046
|
delayMs: delay,
|
|
1701
2047
|
error: new Error(`Retryable: ${response.status}`)
|
|
1702
2048
|
});
|
|
1703
|
-
await sleep(delay);
|
|
2049
|
+
await sleep(delay, signal);
|
|
1704
2050
|
continue;
|
|
1705
2051
|
}
|
|
1706
2052
|
if (response.status >= 400 && response.status < 500) {
|
|
@@ -1718,22 +2064,22 @@ var CimplifyClient = class {
|
|
|
1718
2064
|
});
|
|
1719
2065
|
return response;
|
|
1720
2066
|
}
|
|
1721
|
-
if (response.status >= 500 && attempt <
|
|
2067
|
+
if (response.status >= 500 && attempt < maxRetries) {
|
|
1722
2068
|
retryCount++;
|
|
1723
|
-
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
2069
|
+
const delay = withPositiveJitter(this.retryDelay * Math.pow(2, attempt), jitterRatio);
|
|
1724
2070
|
this.hooks.onRetry?.({
|
|
1725
2071
|
...context,
|
|
1726
2072
|
attempt: retryCount,
|
|
1727
2073
|
delayMs: delay,
|
|
1728
2074
|
error: new Error(`Server error: ${response.status}`)
|
|
1729
2075
|
});
|
|
1730
|
-
await sleep(delay);
|
|
2076
|
+
await sleep(delay, signal);
|
|
1731
2077
|
continue;
|
|
1732
2078
|
}
|
|
1733
2079
|
this.hooks.onRequestError?.({
|
|
1734
2080
|
...context,
|
|
1735
2081
|
error: new CimplifyError(
|
|
1736
|
-
|
|
2082
|
+
ErrorCode.SERVER_ERROR,
|
|
1737
2083
|
`Server error ${response.status} after ${retryCount} retries`,
|
|
1738
2084
|
false
|
|
1739
2085
|
),
|
|
@@ -1745,9 +2091,12 @@ var CimplifyClient = class {
|
|
|
1745
2091
|
return response;
|
|
1746
2092
|
} catch (error) {
|
|
1747
2093
|
lastError = error;
|
|
2094
|
+
if (signal?.aborted) {
|
|
2095
|
+
throw new CimplifyError(ErrorCode.CANCELLED, "Request was cancelled.", true);
|
|
2096
|
+
}
|
|
1748
2097
|
const networkError = toNetworkError(error, this.isTestMode());
|
|
1749
2098
|
const errorRetryable = isRetryable(error);
|
|
1750
|
-
if (!errorRetryable || attempt >=
|
|
2099
|
+
if (!errorRetryable || attempt >= maxRetries) {
|
|
1751
2100
|
this.hooks.onRequestError?.({
|
|
1752
2101
|
...context,
|
|
1753
2102
|
error: networkError,
|
|
@@ -1758,14 +2107,14 @@ var CimplifyClient = class {
|
|
|
1758
2107
|
throw networkError;
|
|
1759
2108
|
}
|
|
1760
2109
|
retryCount++;
|
|
1761
|
-
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
2110
|
+
const delay = withPositiveJitter(this.retryDelay * Math.pow(2, attempt), jitterRatio);
|
|
1762
2111
|
this.hooks.onRetry?.({
|
|
1763
2112
|
...context,
|
|
1764
2113
|
attempt: retryCount,
|
|
1765
2114
|
delayMs: delay,
|
|
1766
2115
|
error: networkError
|
|
1767
2116
|
});
|
|
1768
|
-
await sleep(delay);
|
|
2117
|
+
await sleep(delay, signal);
|
|
1769
2118
|
}
|
|
1770
2119
|
}
|
|
1771
2120
|
const finalError = toNetworkError(lastError, this.isTestMode());
|
|
@@ -1802,16 +2151,24 @@ var CimplifyClient = class {
|
|
|
1802
2151
|
return request;
|
|
1803
2152
|
}
|
|
1804
2153
|
async get(path, opts) {
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
2154
|
+
const request = async () => {
|
|
2155
|
+
const response = await this.authedFetch(
|
|
2156
|
+
this.buildUrl(this.baseUrl, path),
|
|
2157
|
+
{
|
|
2158
|
+
method: "GET",
|
|
2159
|
+
credentials: this.credentials,
|
|
2160
|
+
headers: this.getHeaders(),
|
|
2161
|
+
...nextInit(opts?.cacheOptions)
|
|
2162
|
+
},
|
|
2163
|
+
opts?.timeoutMs,
|
|
2164
|
+
opts?.retryPolicy,
|
|
2165
|
+
opts?.signal
|
|
2166
|
+
);
|
|
1813
2167
|
return this.handleRestResponse(response);
|
|
1814
|
-
}
|
|
2168
|
+
};
|
|
2169
|
+
if (opts?.signal) return request();
|
|
2170
|
+
const key = this.getDedupeKey("get", path, opts?.cacheOptions);
|
|
2171
|
+
return this.deduplicatedRequest(key, request);
|
|
1815
2172
|
}
|
|
1816
2173
|
async post(path, body, opts) {
|
|
1817
2174
|
const response = await this.authedFetch(
|
|
@@ -1822,7 +2179,9 @@ var CimplifyClient = class {
|
|
|
1822
2179
|
headers: mergeOptionHeaders(this.getHeaders(), opts),
|
|
1823
2180
|
body: body ? JSON.stringify(body) : void 0
|
|
1824
2181
|
},
|
|
1825
|
-
opts?.timeoutMs
|
|
2182
|
+
opts?.timeoutMs,
|
|
2183
|
+
opts?.retryPolicy,
|
|
2184
|
+
opts?.signal
|
|
1826
2185
|
);
|
|
1827
2186
|
return this.handleRestResponse(response);
|
|
1828
2187
|
}
|
|
@@ -1835,7 +2194,9 @@ var CimplifyClient = class {
|
|
|
1835
2194
|
headers: mergeOptionHeaders(this.getHeaders(), opts),
|
|
1836
2195
|
body: body ? JSON.stringify(body) : void 0
|
|
1837
2196
|
},
|
|
1838
|
-
opts?.timeoutMs
|
|
2197
|
+
opts?.timeoutMs,
|
|
2198
|
+
opts?.retryPolicy,
|
|
2199
|
+
opts?.signal
|
|
1839
2200
|
);
|
|
1840
2201
|
return this.handleRestResponse(response);
|
|
1841
2202
|
}
|
|
@@ -1847,21 +2208,31 @@ var CimplifyClient = class {
|
|
|
1847
2208
|
credentials: this.credentials,
|
|
1848
2209
|
headers: mergeOptionHeaders(this.getHeaders(), opts)
|
|
1849
2210
|
},
|
|
1850
|
-
opts?.timeoutMs
|
|
2211
|
+
opts?.timeoutMs,
|
|
2212
|
+
opts?.retryPolicy,
|
|
2213
|
+
opts?.signal
|
|
1851
2214
|
);
|
|
1852
2215
|
return this.handleRestResponse(response);
|
|
1853
2216
|
}
|
|
1854
2217
|
async linkGet(path, opts) {
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2218
|
+
const request = async () => {
|
|
2219
|
+
const response = await this.authedFetch(
|
|
2220
|
+
`${this.linkApiUrl}${path}`,
|
|
2221
|
+
{
|
|
2222
|
+
method: "GET",
|
|
2223
|
+
credentials: this.credentials,
|
|
2224
|
+
headers: this.getHeaders(),
|
|
2225
|
+
...nextInit(opts?.cacheOptions)
|
|
2226
|
+
},
|
|
2227
|
+
opts?.timeoutMs,
|
|
2228
|
+
opts?.retryPolicy,
|
|
2229
|
+
opts?.signal
|
|
2230
|
+
);
|
|
1863
2231
|
return this.handleRestResponse(response);
|
|
1864
|
-
}
|
|
2232
|
+
};
|
|
2233
|
+
if (opts?.signal) return request();
|
|
2234
|
+
const key = this.getDedupeKey("linkGet", path, opts?.cacheOptions);
|
|
2235
|
+
return this.deduplicatedRequest(key, request);
|
|
1865
2236
|
}
|
|
1866
2237
|
async linkPost(path, body, opts) {
|
|
1867
2238
|
const response = await this.authedFetch(
|
|
@@ -1872,25 +2243,33 @@ var CimplifyClient = class {
|
|
|
1872
2243
|
headers: mergeOptionHeaders(this.getHeaders(), opts),
|
|
1873
2244
|
body: body ? JSON.stringify(body) : void 0
|
|
1874
2245
|
},
|
|
1875
|
-
opts?.timeoutMs
|
|
2246
|
+
opts?.timeoutMs,
|
|
2247
|
+
opts?.retryPolicy,
|
|
2248
|
+
opts?.signal
|
|
1876
2249
|
);
|
|
1877
2250
|
return this.handleRestResponse(response);
|
|
1878
2251
|
}
|
|
1879
2252
|
async linkDelete(path, opts) {
|
|
1880
|
-
const response = await this.authedFetch(
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2253
|
+
const response = await this.authedFetch(
|
|
2254
|
+
`${this.linkApiUrl}${path}`,
|
|
2255
|
+
{
|
|
2256
|
+
method: "DELETE",
|
|
2257
|
+
credentials: this.credentials,
|
|
2258
|
+
headers: mergeOptionHeaders(this.getHeaders(), opts)
|
|
2259
|
+
},
|
|
2260
|
+
opts?.timeoutMs,
|
|
2261
|
+
opts?.retryPolicy,
|
|
2262
|
+
opts?.signal
|
|
2263
|
+
);
|
|
1885
2264
|
return this.handleRestResponse(response);
|
|
1886
2265
|
}
|
|
1887
2266
|
async handleRestResponse(response) {
|
|
1888
2267
|
const json = await response.json();
|
|
1889
2268
|
if (!response.ok) {
|
|
1890
|
-
const errorCode = typeof json.error === "object" && json.error?.error_code || typeof json.error === "object" && json.error?.code ||
|
|
2269
|
+
const errorCode = typeof json.error === "object" && json.error?.error_code || typeof json.error === "object" && json.error?.code || ErrorCode.API_ERROR;
|
|
1891
2270
|
const errorMessage = typeof json.error === "object" && json.error?.error_message || typeof json.error === "object" && json.error?.message || typeof json.error === "string" && json.error || typeof json.message === "string" && json.message || "An error occurred";
|
|
1892
2271
|
const retryable = typeof json.error === "object" && typeof json.error?.retryable === "boolean" ? json.error.retryable : false;
|
|
1893
|
-
if (errorCode ===
|
|
2272
|
+
if (errorCode === ErrorCode.IDEMPOTENCY_MISMATCH) {
|
|
1894
2273
|
const originalCreatedAt = typeof json.error === "object" && json.error?.original_created_at || "";
|
|
1895
2274
|
throw new IdempotencyMismatchError(errorMessage, originalCreatedAt);
|
|
1896
2275
|
}
|
|
@@ -1907,7 +2286,7 @@ var CimplifyClient = class {
|
|
|
1907
2286
|
if (json?.success === false || json?.error?.code || json?.error?.message) {
|
|
1908
2287
|
const error = enrichError(
|
|
1909
2288
|
new CimplifyError(
|
|
1910
|
-
json.error?.code ||
|
|
2289
|
+
json.error?.code || ErrorCode.API_ERROR,
|
|
1911
2290
|
json.error?.message || "An error occurred",
|
|
1912
2291
|
json.error?.retryable || false
|
|
1913
2292
|
),
|
|
@@ -1938,6 +2317,12 @@ var CimplifyClient = class {
|
|
|
1938
2317
|
}
|
|
1939
2318
|
return this._checkout;
|
|
1940
2319
|
}
|
|
2320
|
+
get purchaseIntents() {
|
|
2321
|
+
if (!this._purchaseIntents) {
|
|
2322
|
+
this._purchaseIntents = new PurchaseIntentService(this);
|
|
2323
|
+
}
|
|
2324
|
+
return this._purchaseIntents;
|
|
2325
|
+
}
|
|
1941
2326
|
get orders() {
|
|
1942
2327
|
if (!this._orders) {
|
|
1943
2328
|
this._orders = new OrderQueries(this);
|
|
@@ -2048,4 +2433,4 @@ function createCimplifyClient(config = {}) {
|
|
|
2048
2433
|
return new CimplifyClient(config);
|
|
2049
2434
|
}
|
|
2050
2435
|
|
|
2051
|
-
export { AUTH_TOKEN_CHANGED, ActivityService, CimplifyClient, DeliveryService,
|
|
2436
|
+
export { AUTH_TOKEN_CHANGED, ActivityService, CimplifyClient, DeliveryService, HostedPurchaseIntentService, MUTATION_ERROR, PURCHASE_INTENT_ROUTE, PlacesService, PurchaseIntentResolver, PurchaseIntentService, SESSION_CHANGE_SOURCE, SESSION_CLAIMED, SESSION_CLEARED, StoreCreditQueries, SubscriptionService, SupportService, UploadService, chatRealtimeUpdateFromWire, createCimplifyClient };
|