@wix/himalaya-cli 0.1141.0 → 0.1143.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/delivery-contract.d.ts +104 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/delivery.d.ts +33 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/index.d.ts +5 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +509 -3
- package/dist/cli.mjs +31398 -30604
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +1 -1
- package/guides/getting-started.md +29 -11
- package/guides/himi-skill.md +29 -11
- package/guides/sdk-reference.json +132 -0
- package/guides/templates.md +33 -0
- package/guides/trim-a-template.md +13 -0
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +2 -0
- package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/branded-lite/dev/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/branded-lite/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/branded-lite/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/community-club/dev/index.ts +2 -1
- package/templates/community-club/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/community-club/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/events-meetups/dev/index.ts +2 -1
- package/templates/events-meetups/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/events-meetups/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/gym-fitness/dev/index.ts +3 -1
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/gym-fitness/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/nonprofit-cause/dev/index.ts +2 -1
- package/templates/nonprofit-cause/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/podcast-creator/dev/index.ts +2 -1
- package/templates/podcast-creator/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/portfolio-creative/dev/index.ts +2 -1
- package/templates/portfolio-creative/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/restaurant/dev/index.ts +2 -1
- package/templates/restaurant/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/restaurant/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/retail-store/dev/index.ts +1 -0
- package/templates/retail-store/tier5-src/himi-store-account/index.ts +7 -1
- package/templates/yoga-studio/dev/index.ts +3 -1
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +2 -2
- package/templates/yoga-studio/tier5-src/himi-events-rsvp/index.ts +2 -2
- package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +7 -1
|
@@ -13,6 +13,507 @@ function dispatch(handlerId, params, _options) {
|
|
|
13
13
|
return himalaya.dispatch(handlerId, stringParams);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// ../../core/sdk/iap/src/delivery.ts
|
|
17
|
+
var MAX_AUTOMATIC_ATTEMPTS = 5;
|
|
18
|
+
var MAX_RETRY_DELAY_MS = 6e4;
|
|
19
|
+
var BASE_RETRY_DELAY_MS = 1e3;
|
|
20
|
+
var UNAVAILABLE_CODES = /* @__PURE__ */ new Set(["unsupported", "unavailable", "notEnabled"]);
|
|
21
|
+
function createPurchaseCoordinator(deps) {
|
|
22
|
+
const items = /* @__PURE__ */ new Map();
|
|
23
|
+
const queue = createSerialQueue();
|
|
24
|
+
const store = guardStore(deps.store);
|
|
25
|
+
let outcome = "idle";
|
|
26
|
+
let purchasing = false;
|
|
27
|
+
let signInPrompt = false;
|
|
28
|
+
let unavailable = null;
|
|
29
|
+
let pendingApproval = null;
|
|
30
|
+
let errorCode = null;
|
|
31
|
+
let errorFromReconcile = false;
|
|
32
|
+
let unfinishedCount = null;
|
|
33
|
+
let generation = deps.session.current().generation;
|
|
34
|
+
let staleSignal = createSignal();
|
|
35
|
+
let started = false;
|
|
36
|
+
let disposed = false;
|
|
37
|
+
const unsubscribe = deps.session.subscribe(onSessionNotified);
|
|
38
|
+
function start() {
|
|
39
|
+
started = true;
|
|
40
|
+
return reconcile();
|
|
41
|
+
}
|
|
42
|
+
function purchase2(catalogRef) {
|
|
43
|
+
onSessionNotified();
|
|
44
|
+
const requestedBy = deps.session.current();
|
|
45
|
+
const name = `purchase:${requestedBy.generation}:${catalogRef}`;
|
|
46
|
+
return runJob(name, () => runPurchase(requestedBy, catalogRef), { joinRunning: true }).then(([dropped, state]) => dropped ? { ...state, errorCode: "sessionChanged" } : state);
|
|
47
|
+
}
|
|
48
|
+
function reconcile() {
|
|
49
|
+
resetIfSessionChanged();
|
|
50
|
+
return enqueue("reconcile", runReconcile);
|
|
51
|
+
}
|
|
52
|
+
function retry() {
|
|
53
|
+
resetIfSessionChanged();
|
|
54
|
+
return enqueue("retry", runRetry);
|
|
55
|
+
}
|
|
56
|
+
function tick() {
|
|
57
|
+
if (resetIfSessionChanged() && started) return reconcile();
|
|
58
|
+
return enqueue("tick", runDue);
|
|
59
|
+
}
|
|
60
|
+
function getState() {
|
|
61
|
+
if (disposed || !sessionMatches()) return { phase: "idle", errorCode: null, items: [], unfinishedCount: null, nextWakeAt: null };
|
|
62
|
+
const snapshot = [...items.values()].map((item) => ({ ...item.state }));
|
|
63
|
+
const due = snapshot.map((item) => item.nextAttemptAt).filter((at) => at !== null);
|
|
64
|
+
return { phase: currentPhase(), errorCode: errorCode ?? unavailable?.code ?? null, items: snapshot, unfinishedCount, nextWakeAt: due.length > 0 ? Math.min(...due) : null };
|
|
65
|
+
}
|
|
66
|
+
function dispose() {
|
|
67
|
+
disposed = true;
|
|
68
|
+
unsubscribe();
|
|
69
|
+
staleSignal.fire();
|
|
70
|
+
}
|
|
71
|
+
async function runPurchase(session, catalogRef) {
|
|
72
|
+
if (!isCurrent(session)) return !disposed;
|
|
73
|
+
setError(null, false);
|
|
74
|
+
outcome = "idle";
|
|
75
|
+
pendingApproval = null;
|
|
76
|
+
unavailable = null;
|
|
77
|
+
releaseSignIn();
|
|
78
|
+
if (session.kind !== "member") {
|
|
79
|
+
requireSignIn();
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const context = await purchaseContext(session, catalogRef);
|
|
83
|
+
if (!isCurrent(session)) return !disposed;
|
|
84
|
+
if (!context) return false;
|
|
85
|
+
purchasing = true;
|
|
86
|
+
publish();
|
|
87
|
+
const { productId, appAccountToken } = context;
|
|
88
|
+
const result = await store.purchaseForVerification({ productId, appAccountToken });
|
|
89
|
+
purchasing = false;
|
|
90
|
+
if (!isCurrent(session)) return !disposed;
|
|
91
|
+
if (result.status === "purchased") await deliverPurchase(session, result.transaction, context);
|
|
92
|
+
else if (result.status === "failed") failStore(result.code, false);
|
|
93
|
+
else if (result.status === "pending") {
|
|
94
|
+
if (!await awaitApproval(session, context)) return !disposed;
|
|
95
|
+
} else setOutcome("cancelled");
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
async function deliverPurchase(session, transaction, context) {
|
|
99
|
+
const item = upsert(transaction, null);
|
|
100
|
+
applyDelivered(await deliver(session, [item], context));
|
|
101
|
+
await enumerate(session);
|
|
102
|
+
publish();
|
|
103
|
+
}
|
|
104
|
+
async function runReconcile() {
|
|
105
|
+
resetIfSessionChanged();
|
|
106
|
+
const session = deps.session.current();
|
|
107
|
+
const listing = await enumerate(session);
|
|
108
|
+
if (listing === "stale") return;
|
|
109
|
+
if (session.kind !== "member") return requireSignIn();
|
|
110
|
+
if (listing === "listed") clearReconcileProblem();
|
|
111
|
+
const now = deps.clock.now();
|
|
112
|
+
const eligible = itemsWhere((state) => isAwaitingDelivery(state) && (state.nextAttemptAt === null || state.nextAttemptAt <= now));
|
|
113
|
+
eligible.forEach(restartAttemptBudget);
|
|
114
|
+
await settleDelivery(session, await deliver(session, eligible));
|
|
115
|
+
}
|
|
116
|
+
async function settleDelivery(session, delivered) {
|
|
117
|
+
applyDelivered(delivered);
|
|
118
|
+
if (delivered.finished) await enumerate(session);
|
|
119
|
+
publish();
|
|
120
|
+
}
|
|
121
|
+
function applyDelivered(delivered) {
|
|
122
|
+
if (delivered.confirmedProducts.length === 0) return;
|
|
123
|
+
if (outcome === "cancelled") outcome = "idle";
|
|
124
|
+
if (!delivered.signInLost) releaseSignIn();
|
|
125
|
+
}
|
|
126
|
+
function releaseSignIn() {
|
|
127
|
+
if (!signInPrompt) return;
|
|
128
|
+
signInPrompt = false;
|
|
129
|
+
const now = deps.clock.now();
|
|
130
|
+
for (const item of items.values()) {
|
|
131
|
+
if (item.state.access === "signInRequired") Object.assign(item.state, { access: "waitingToRetry", attempts: 0, nextAttemptAt: now });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function clearReconcileProblem() {
|
|
135
|
+
if (unavailable?.from === "reconcile") unavailable = null;
|
|
136
|
+
if (errorFromReconcile) setError(null, false);
|
|
137
|
+
}
|
|
138
|
+
async function runRetry() {
|
|
139
|
+
releaseSignIn();
|
|
140
|
+
for (const item of items.values()) unblock(item.state);
|
|
141
|
+
await runReconcile();
|
|
142
|
+
}
|
|
143
|
+
async function runDue() {
|
|
144
|
+
if (resetIfSessionChanged()) return started ? runReconcile() : void 0;
|
|
145
|
+
const session = memberSession();
|
|
146
|
+
const now = deps.clock.now();
|
|
147
|
+
const due = itemsWhere((state) => state.nextAttemptAt !== null && state.nextAttemptAt <= now);
|
|
148
|
+
if (!session || due.length === 0) return;
|
|
149
|
+
await settleDelivery(session, await deliver(session, due));
|
|
150
|
+
}
|
|
151
|
+
async function deliver(session, batch, known) {
|
|
152
|
+
const needProof = batch.filter((item) => item.state.access !== "confirmed");
|
|
153
|
+
const context = known ?? (needProof.length > 0 ? await memberContext(session, needProof) : void 0);
|
|
154
|
+
const delivered = noDelivery();
|
|
155
|
+
for (const item of batch) {
|
|
156
|
+
if (!isCurrent(session)) return noDelivery();
|
|
157
|
+
const wasConfirmed = item.state.access === "confirmed";
|
|
158
|
+
if (delivered.signInLost && !wasConfirmed) {
|
|
159
|
+
blockWithoutSession(item, context);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (await deliverItem(session, item, context)) delivered.finished = true;
|
|
163
|
+
if (!wasConfirmed && item.state.access === "confirmed") delivered.confirmedProducts.push(item.state.productId);
|
|
164
|
+
if (item.state.access === "signInRequired") delivered.signInLost = true;
|
|
165
|
+
}
|
|
166
|
+
return delivered;
|
|
167
|
+
}
|
|
168
|
+
function blockWithoutSession(item, context) {
|
|
169
|
+
const foreign = context !== void 0 && !sameAccount(item.transaction.appAccountToken, context.appAccountToken);
|
|
170
|
+
block(item.state, foreign ? "ownershipConflict" : "signInRequired", null);
|
|
171
|
+
}
|
|
172
|
+
async function deliverItem(session, item, context) {
|
|
173
|
+
if (item.state.access !== "confirmed") {
|
|
174
|
+
if (!context || !await confirmAccess(session, item, context)) return false;
|
|
175
|
+
}
|
|
176
|
+
return complete(session, item);
|
|
177
|
+
}
|
|
178
|
+
async function confirmAccess(session, item, context) {
|
|
179
|
+
if (!sameAccount(item.transaction.appAccountToken, context.appAccountToken)) {
|
|
180
|
+
block(item.state, "ownershipConflict", null);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
updateItem(item, { access: "confirming", nextAttemptAt: null });
|
|
184
|
+
const settled = await settle(session, () => deps.backend.submitProof(session, item.transaction));
|
|
185
|
+
if (settled.kind === "stale") return false;
|
|
186
|
+
if (settled.kind === "error") return recordFailedAttempt(item, void 0);
|
|
187
|
+
return applySubmission(item, settled.value, context.scopeKey);
|
|
188
|
+
}
|
|
189
|
+
function applySubmission(item, raw, scopeKey) {
|
|
190
|
+
if (!isObject(raw)) return block(item.state, "rejected", "invalidResponse");
|
|
191
|
+
switch (raw.status) {
|
|
192
|
+
case "processed":
|
|
193
|
+
case "alreadyProcessed":
|
|
194
|
+
if (!acknowledges(raw.acknowledgement, item.transaction, scopeKey)) return block(item.state, "rejected", "invalidResponse");
|
|
195
|
+
Object.assign(item.state, { access: "confirmed", nextAttemptAt: null, errorCode: null });
|
|
196
|
+
return true;
|
|
197
|
+
case "retryable":
|
|
198
|
+
return recordFailedAttempt(item, retryAfter(raw.retryAfterMs));
|
|
199
|
+
case "authRequired":
|
|
200
|
+
signInPrompt = true;
|
|
201
|
+
return block(item.state, "signInRequired", null);
|
|
202
|
+
case "ownershipConflict":
|
|
203
|
+
return block(item.state, "ownershipConflict", null);
|
|
204
|
+
case "rejected":
|
|
205
|
+
return block(item.state, "rejected", "proofRejected");
|
|
206
|
+
default:
|
|
207
|
+
return block(item.state, "rejected", "invalidResponse");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function complete(session, item) {
|
|
211
|
+
if (!isCurrent(session)) return false;
|
|
212
|
+
const result = await store.finishTransaction({ transactionId: item.transaction.transactionId });
|
|
213
|
+
if (!isCurrent(session)) return false;
|
|
214
|
+
if (result.status === "failed") {
|
|
215
|
+
item.state.completion = "retryRequired";
|
|
216
|
+
recordFailedAttempt(item, void 0);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
Object.assign(item.state, { completion: result.status, held: false, nextAttemptAt: null });
|
|
220
|
+
return result.status === "finished";
|
|
221
|
+
}
|
|
222
|
+
function recordFailedAttempt(item, retryAfterMs) {
|
|
223
|
+
const state = item.state;
|
|
224
|
+
state.attempts += 1;
|
|
225
|
+
if (state.access !== "confirmed") state.access = "waitingToRetry";
|
|
226
|
+
state.nextAttemptAt = state.attempts >= MAX_AUTOMATIC_ATTEMPTS ? null : deps.clock.now() + retryDelay(state.attempts, retryAfterMs);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function retryDelay(attempts, retryAfterMs) {
|
|
230
|
+
if (retryAfterMs !== void 0) return Math.min(retryAfterMs, MAX_RETRY_DELAY_MS);
|
|
231
|
+
const base = Math.min(BASE_RETRY_DELAY_MS * 2 ** (attempts - 1), MAX_RETRY_DELAY_MS);
|
|
232
|
+
return base + Math.max(0, deps.jitter?.(base) ?? 0);
|
|
233
|
+
}
|
|
234
|
+
async function purchaseContext(session, catalogRef) {
|
|
235
|
+
const settled = await settle(session, () => deps.backend.getPurchaseContext(session, catalogRef));
|
|
236
|
+
if (settled.kind === "stale") return null;
|
|
237
|
+
const decoded = decodeContext(settled, isPurchaseContext);
|
|
238
|
+
if (decoded.kind === "ok") return decoded.context;
|
|
239
|
+
if (decoded.kind === "signInRequired") requireSignIn();
|
|
240
|
+
else failContext(decoded.kind);
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
async function memberContext(session, waiting) {
|
|
244
|
+
const settled = await settle(session, () => deps.backend.getMemberContext(session));
|
|
245
|
+
if (settled.kind === "stale") return void 0;
|
|
246
|
+
const decoded = decodeContext(settled, isMemberContext);
|
|
247
|
+
if (decoded.kind === "ok") return decoded.context;
|
|
248
|
+
if (decoded.kind === "rejected") setError("contextRejected", true);
|
|
249
|
+
if (decoded.kind === "signInRequired") signInPrompt = true;
|
|
250
|
+
for (const item of waiting) {
|
|
251
|
+
if (decoded.kind === "unavailable") recordFailedAttempt(item, decoded.retryAfterMs);
|
|
252
|
+
else if (decoded.kind === "rejected") block(item.state, "waitingToRetry", "contextRejected");
|
|
253
|
+
else block(item.state, "signInRequired", null);
|
|
254
|
+
}
|
|
255
|
+
return void 0;
|
|
256
|
+
}
|
|
257
|
+
async function enumerate(session, { quiet = false } = {}) {
|
|
258
|
+
if (!isCurrent(session)) return "stale";
|
|
259
|
+
const result = await store.getUnfinishedTransactions();
|
|
260
|
+
if (!isCurrent(session)) return "stale";
|
|
261
|
+
if (result.status !== "ok") {
|
|
262
|
+
unfinishedCount = null;
|
|
263
|
+
if (!quiet) failStore(result.code, true);
|
|
264
|
+
return "failed";
|
|
265
|
+
}
|
|
266
|
+
const held = new Set(result.transactions.map((transaction) => upsert(transaction, true)));
|
|
267
|
+
for (const item of items.values()) if (!held.has(item)) item.state.held = false;
|
|
268
|
+
unfinishedCount = result.transactions.length;
|
|
269
|
+
return "listed";
|
|
270
|
+
}
|
|
271
|
+
function upsert(transaction, held) {
|
|
272
|
+
const key = identityOf(transaction);
|
|
273
|
+
const existing = items.get(key);
|
|
274
|
+
if (existing) {
|
|
275
|
+
existing.transaction = transaction;
|
|
276
|
+
if (held !== null) existing.state.held = held;
|
|
277
|
+
return existing;
|
|
278
|
+
}
|
|
279
|
+
if (pendingApproval?.productId === transaction.productId && !pendingApproval.known.has(key) && sameAccount(transaction.appAccountToken, pendingApproval.token)) {
|
|
280
|
+
pendingApproval = null;
|
|
281
|
+
if (outcome === "awaitingApproval") outcome = "idle";
|
|
282
|
+
}
|
|
283
|
+
const item = { transaction, state: newItemState(transaction, held) };
|
|
284
|
+
items.set(key, item);
|
|
285
|
+
return item;
|
|
286
|
+
}
|
|
287
|
+
function onSessionNotified() {
|
|
288
|
+
if (resetIfSessionChanged() && started) void reconcile();
|
|
289
|
+
}
|
|
290
|
+
function resetIfSessionChanged() {
|
|
291
|
+
const next = deps.session.current().generation;
|
|
292
|
+
if (disposed || next === generation) return false;
|
|
293
|
+
generation = next;
|
|
294
|
+
staleSignal.fire();
|
|
295
|
+
staleSignal = createSignal();
|
|
296
|
+
items.clear();
|
|
297
|
+
outcome = "idle";
|
|
298
|
+
purchasing = false;
|
|
299
|
+
signInPrompt = false;
|
|
300
|
+
unavailable = null;
|
|
301
|
+
pendingApproval = null;
|
|
302
|
+
unfinishedCount = null;
|
|
303
|
+
setError(null, false);
|
|
304
|
+
publish();
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
function memberSession() {
|
|
308
|
+
const session = deps.session.current();
|
|
309
|
+
return session.kind === "member" ? session : null;
|
|
310
|
+
}
|
|
311
|
+
function sessionMatches() {
|
|
312
|
+
return deps.session.current().generation === generation;
|
|
313
|
+
}
|
|
314
|
+
function isCurrent(session) {
|
|
315
|
+
return !disposed && session.generation === generation && sessionMatches();
|
|
316
|
+
}
|
|
317
|
+
async function settle(session, call) {
|
|
318
|
+
const stale = staleSignal.waiter();
|
|
319
|
+
try {
|
|
320
|
+
const pending = Promise.resolve().then(() => {
|
|
321
|
+
if (!isCurrent(session)) throw ABANDONED;
|
|
322
|
+
return call();
|
|
323
|
+
});
|
|
324
|
+
const settled = await Promise.race([pending.then((value) => ({ kind: "value", value })), stale.fired.then(() => STALE)]);
|
|
325
|
+
return isCurrent(session) ? settled : STALE;
|
|
326
|
+
} catch {
|
|
327
|
+
return isCurrent(session) ? { kind: "error" } : STALE;
|
|
328
|
+
} finally {
|
|
329
|
+
stale.cancel();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function runJob(name, run, options = {}) {
|
|
333
|
+
return queue.run(name, () => disposed ? Promise.resolve(void 0) : run(), options).then((result) => [result, getState()]);
|
|
334
|
+
}
|
|
335
|
+
function enqueue(name, run) {
|
|
336
|
+
return runJob(name, run).then(([, state]) => state);
|
|
337
|
+
}
|
|
338
|
+
function itemsWhere(predicate) {
|
|
339
|
+
return [...items.values()].filter((item) => predicate(item.state));
|
|
340
|
+
}
|
|
341
|
+
function updateItem(item, patch) {
|
|
342
|
+
Object.assign(item.state, patch);
|
|
343
|
+
publish();
|
|
344
|
+
}
|
|
345
|
+
function requireSignIn() {
|
|
346
|
+
for (const item of items.values()) {
|
|
347
|
+
if (isAwaitingDelivery(item.state) && item.state.access !== "confirmed") block(item.state, "signInRequired", null);
|
|
348
|
+
}
|
|
349
|
+
signInPrompt = true;
|
|
350
|
+
publish();
|
|
351
|
+
}
|
|
352
|
+
function failContext(reason) {
|
|
353
|
+
setError(reason === "rejected" ? "contextRejected" : "contextUnavailable", false);
|
|
354
|
+
publish();
|
|
355
|
+
}
|
|
356
|
+
function failStore(code, fromReconcile) {
|
|
357
|
+
setError(code, fromReconcile);
|
|
358
|
+
if (UNAVAILABLE_CODES.has(code) && unavailable?.from !== "purchase") {
|
|
359
|
+
unavailable = { from: fromReconcile ? "reconcile" : "purchase", code };
|
|
360
|
+
}
|
|
361
|
+
publish();
|
|
362
|
+
}
|
|
363
|
+
async function awaitApproval(session, context) {
|
|
364
|
+
if (await enumerate(session, { quiet: true }) === "stale") return false;
|
|
365
|
+
const known = new Set([...items.keys()].filter((key) => items.get(key).state.productId === context.productId));
|
|
366
|
+
pendingApproval = { productId: context.productId, token: context.appAccountToken, known };
|
|
367
|
+
setOutcome("awaitingApproval");
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
function setOutcome(next) {
|
|
371
|
+
outcome = next;
|
|
372
|
+
publish();
|
|
373
|
+
}
|
|
374
|
+
function currentPhase() {
|
|
375
|
+
if (purchasing) return "purchasing";
|
|
376
|
+
if (unavailable?.from === "purchase") return "unavailable";
|
|
377
|
+
if (signInPrompt) return "signInRequired";
|
|
378
|
+
if (unavailable?.from === "reconcile") return "unavailable";
|
|
379
|
+
return outcome;
|
|
380
|
+
}
|
|
381
|
+
function setError(code, fromReconcile) {
|
|
382
|
+
errorCode = code;
|
|
383
|
+
errorFromReconcile = code !== null && fromReconcile;
|
|
384
|
+
}
|
|
385
|
+
function publish() {
|
|
386
|
+
if (!disposed && sessionMatches()) deps.onState?.(getState());
|
|
387
|
+
}
|
|
388
|
+
return { start, purchase: purchase2, reconcile, retry, tick, getState, dispose };
|
|
389
|
+
}
|
|
390
|
+
var noDelivery = () => ({ finished: false, confirmedProducts: [], signInLost: false });
|
|
391
|
+
var STALE = { kind: "stale" };
|
|
392
|
+
var ABANDONED = { abandoned: true };
|
|
393
|
+
async function storeCall(call) {
|
|
394
|
+
try {
|
|
395
|
+
return await call();
|
|
396
|
+
} catch {
|
|
397
|
+
return { status: "failed", code: "storeError" };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function guardStore(store) {
|
|
401
|
+
return {
|
|
402
|
+
purchaseForVerification: (input) => storeCall(() => store.purchaseForVerification(input)),
|
|
403
|
+
getUnfinishedTransactions: () => storeCall(() => store.getUnfinishedTransactions()),
|
|
404
|
+
finishTransaction: (input) => storeCall(() => store.finishTransaction(input))
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function identityOf(transaction) {
|
|
408
|
+
return `${transaction.store}:${transaction.environment}:${transaction.transactionId}`;
|
|
409
|
+
}
|
|
410
|
+
function newItemState(transaction, held) {
|
|
411
|
+
return {
|
|
412
|
+
productId: transaction.productId,
|
|
413
|
+
environment: transaction.environment,
|
|
414
|
+
held,
|
|
415
|
+
access: "pending",
|
|
416
|
+
completion: "pending",
|
|
417
|
+
attempts: 0,
|
|
418
|
+
nextAttemptAt: null,
|
|
419
|
+
errorCode: null
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function isAwaitingDelivery(state) {
|
|
423
|
+
if (state.access === "confirmed") return state.completion === "pending" || state.completion === "retryRequired";
|
|
424
|
+
return state.access === "pending" || state.access === "waitingToRetry" || state.access === "signInRequired";
|
|
425
|
+
}
|
|
426
|
+
function restartAttemptBudget(item) {
|
|
427
|
+
item.state.attempts = 0;
|
|
428
|
+
}
|
|
429
|
+
function unblock(state) {
|
|
430
|
+
if (state.access !== "confirmed") Object.assign(state, { access: "pending", errorCode: null });
|
|
431
|
+
Object.assign(state, { attempts: 0, nextAttemptAt: null });
|
|
432
|
+
}
|
|
433
|
+
function block(state, access, errorCode) {
|
|
434
|
+
Object.assign(state, { access, errorCode, nextAttemptAt: null });
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
function sameAccount(transactionToken, memberToken) {
|
|
438
|
+
return transactionToken !== null && transactionToken.toLowerCase() === memberToken.toLowerCase();
|
|
439
|
+
}
|
|
440
|
+
function acknowledges(acknowledgement, transaction, scopeKey) {
|
|
441
|
+
return isObject(acknowledgement) && acknowledgement.transactionId === transaction.transactionId && acknowledgement.productId === transaction.productId && acknowledgement.environment === transaction.environment && acknowledgement.scopeKey === scopeKey;
|
|
442
|
+
}
|
|
443
|
+
function decodeContext(settled, isContext) {
|
|
444
|
+
const raw = settled.kind === "value" ? settled.value : null;
|
|
445
|
+
if (!isObject(raw)) return { kind: "unavailable" };
|
|
446
|
+
if (raw.status === "ok" && isContext(raw.context)) return { kind: "ok", context: raw.context };
|
|
447
|
+
if (raw.status === "authRequired") return { kind: "signInRequired" };
|
|
448
|
+
if (raw.status === "rejected") return { kind: "rejected" };
|
|
449
|
+
return { kind: "unavailable", retryAfterMs: retryAfter(raw.retryAfterMs) };
|
|
450
|
+
}
|
|
451
|
+
function isPurchaseContext(value) {
|
|
452
|
+
return isMemberContext(value) && isObject(value) && isNonEmpty(value.productId);
|
|
453
|
+
}
|
|
454
|
+
function isMemberContext(value) {
|
|
455
|
+
return isObject(value) && isNonEmpty(value.appAccountToken) && isNonEmpty(value.scopeKey);
|
|
456
|
+
}
|
|
457
|
+
function retryAfter(value) {
|
|
458
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
459
|
+
}
|
|
460
|
+
function isNonEmpty(value) {
|
|
461
|
+
return typeof value === "string" && value.length > 0;
|
|
462
|
+
}
|
|
463
|
+
function isObject(value) {
|
|
464
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
465
|
+
}
|
|
466
|
+
function createSignal() {
|
|
467
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
468
|
+
let hasFired = false;
|
|
469
|
+
return {
|
|
470
|
+
fire() {
|
|
471
|
+
hasFired = true;
|
|
472
|
+
for (const listener of listeners) listener();
|
|
473
|
+
listeners.clear();
|
|
474
|
+
},
|
|
475
|
+
waiter() {
|
|
476
|
+
let listener = () => {
|
|
477
|
+
};
|
|
478
|
+
const fired = new Promise((resolve) => {
|
|
479
|
+
listener = resolve;
|
|
480
|
+
});
|
|
481
|
+
if (hasFired) listener();
|
|
482
|
+
else listeners.add(listener);
|
|
483
|
+
return { fired, cancel: () => {
|
|
484
|
+
listeners.delete(listener);
|
|
485
|
+
} };
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function createSerialQueue() {
|
|
490
|
+
let tail = Promise.resolve();
|
|
491
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
492
|
+
const running = /* @__PURE__ */ new Map();
|
|
493
|
+
async function execute(name, self, job) {
|
|
494
|
+
waiting.delete(name);
|
|
495
|
+
running.set(name, self);
|
|
496
|
+
try {
|
|
497
|
+
return await job();
|
|
498
|
+
} catch {
|
|
499
|
+
return void 0;
|
|
500
|
+
} finally {
|
|
501
|
+
running.delete(name);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
/** Callers joined onto a shared job all receive that job's result. */
|
|
506
|
+
run(name, job, { joinRunning = false }) {
|
|
507
|
+
const shared = waiting.get(name) ?? (joinRunning ? running.get(name) : void 0);
|
|
508
|
+
if (shared) return shared;
|
|
509
|
+
const scheduled = tail.then(() => execute(name, scheduled, job));
|
|
510
|
+
waiting.set(name, scheduled);
|
|
511
|
+
tail = scheduled;
|
|
512
|
+
return scheduled;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
16
517
|
// ../../core/sdk/iap/src/index.ts
|
|
17
518
|
async function getPaywallConfig() {
|
|
18
519
|
const r = await dispatch("capability.iap.getPaywallConfig");
|
|
@@ -81,15 +582,16 @@ async function finishTransaction(input) {
|
|
|
81
582
|
if (result?.status === "finished" || result?.status === "notFound") return { status: result.status };
|
|
82
583
|
return decodeFailure(result);
|
|
83
584
|
}
|
|
585
|
+
var appleManagedStore = { purchaseForVerification, getUnfinishedTransactions, finishTransaction };
|
|
84
586
|
var transactionErrors = /* @__PURE__ */ new Set(["unsupported", "unavailable", "invalidArgument", "invalidResponse", "notEnabled", "unsupportedProductType", "unknownProduct", "verificationFailed", "persistenceFailed", "ownershipConflict", "storeError", "busy"]);
|
|
85
587
|
var failure = (code) => ({ status: "failed", code });
|
|
86
588
|
var nonempty = (value) => typeof value === "string" && value.trim().length > 0;
|
|
87
589
|
var isUUID = (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) && value.length === 36;
|
|
88
|
-
var
|
|
590
|
+
var isObject2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
89
591
|
async function managedDispatch(invoke) {
|
|
90
592
|
try {
|
|
91
593
|
const value = await invoke();
|
|
92
|
-
return
|
|
594
|
+
return isObject2(value) ? value : null;
|
|
93
595
|
} catch {
|
|
94
596
|
return failure("unavailable");
|
|
95
597
|
}
|
|
@@ -98,7 +600,7 @@ function decodeFailure(value) {
|
|
|
98
600
|
return value?.status === "failed" && transactionErrors.has(value.code) ? failure(value.code) : failure("invalidResponse");
|
|
99
601
|
}
|
|
100
602
|
function isAppleTransaction(value) {
|
|
101
|
-
if (!
|
|
603
|
+
if (!isObject2(value)) return false;
|
|
102
604
|
return value.store === "apple" && typeof value.signedTransaction === "string" && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value.signedTransaction) && nonempty(value.transactionId) && nonempty(value.originalTransactionId) && nonempty(value.productId) && (value.appAccountToken === null || isUUID(value.appAccountToken)) && ["production", "sandbox", "xcode", "unknown"].includes(value.environment);
|
|
103
605
|
}
|
|
104
606
|
function decodeTransaction(value) {
|
|
@@ -106,7 +608,11 @@ function decodeTransaction(value) {
|
|
|
106
608
|
return { store, signedTransaction, transactionId, originalTransactionId, productId, appAccountToken, environment };
|
|
107
609
|
}
|
|
108
610
|
export {
|
|
611
|
+
MAX_AUTOMATIC_ATTEMPTS,
|
|
612
|
+
MAX_RETRY_DELAY_MS,
|
|
613
|
+
appleManagedStore,
|
|
109
614
|
consume,
|
|
615
|
+
createPurchaseCoordinator,
|
|
110
616
|
entitlements,
|
|
111
617
|
finishTransaction,
|
|
112
618
|
getPaywallConfig,
|