@withpotter/lib 0.13.0-next.123 → 0.13.0-next.58
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/admin-account-types.d.ts +0 -21
- package/dist/admin-api.d.ts +2 -224
- package/dist/admin-api.js +0 -41
- package/dist/admin-api.js.map +1 -1
- package/dist/admin-case-api.js +4 -1
- package/dist/admin-case-api.js.map +1 -1
- package/dist/admin-pos-api.d.ts +1 -25
- package/dist/admin-pos-api.js +0 -24
- package/dist/admin-pos-api.js.map +1 -1
- package/dist/admin-pos-types.d.ts +0 -43
- package/dist/api/accounts.d.ts +3 -5
- package/dist/api/accounts.js +1 -1
- package/dist/api/accounts.js.map +1 -1
- package/dist/api/analytics.js +3 -1
- package/dist/api/analytics.js.map +1 -1
- package/dist/api/core.d.ts +1 -0
- package/dist/api/core.js +7 -19
- package/dist/api/core.js.map +1 -1
- package/dist/api/potter.js +3 -2
- package/dist/api/potter.js.map +1 -1
- package/dist/api/session-scope.d.ts +2 -2
- package/dist/api/session-scope.js.map +1 -1
- package/dist/api/types/auth.d.ts +1 -7
- package/dist/api/types/auth.js.map +1 -1
- package/dist/api/types/crm.d.ts +0 -2
- package/dist/api/types/templates.d.ts +0 -6
- package/dist/auth-context.d.ts +0 -2
- package/dist/auth-context.js +37 -12
- package/dist/auth-context.js.map +1 -1
- package/dist/mobile-routes.js +2 -4
- package/dist/mobile-routes.js.map +1 -1
- package/dist/plan-price.d.ts +2 -16
- package/dist/plan-price.js +0 -9
- package/dist/plan-price.js.map +1 -1
- package/dist/resolve-enabled-behaviors.d.ts +0 -2
- package/dist/resolve-enabled-behaviors.js +1 -2
- package/dist/resolve-enabled-behaviors.js.map +1 -1
- package/dist/template-types.d.ts +0 -8
- package/dist/timeline-copy.d.ts +1 -1
- package/dist/timeline-copy.js +2 -6
- package/dist/timeline-copy.js.map +1 -1
- package/dist/use-nav-groups.js +5 -5
- package/dist/use-nav-groups.js.map +1 -1
- package/dist/utils.js +4 -4
- package/dist/utils.js.map +1 -1
- package/dist/wallet-format.js +0 -1
- package/dist/wallet-format.js.map +1 -1
- package/package.json +2 -2
- package/dist/__fixtures__/demo-content-server.json +0 -484
- package/dist/admin-earnings-api.d.ts +0 -90
- package/dist/admin-earnings-api.js +0 -15
- package/dist/admin-earnings-api.js.map +0 -1
- package/dist/admin-earnings-model.d.ts +0 -30
- package/dist/admin-earnings-model.js +0 -47
- package/dist/admin-earnings-model.js.map +0 -1
- package/dist/admin-pos-olive.d.ts +0 -44
- package/dist/admin-pos-olive.js +0 -93
- package/dist/admin-pos-olive.js.map +0 -1
- package/dist/api/demo-content.d.ts +0 -104
- package/dist/api/demo-content.js +0 -36
- package/dist/api/demo-content.js.map +0 -1
- package/dist/api/plan-refusal.d.ts +0 -21
- package/dist/api/plan-refusal.js +0 -28
- package/dist/api/plan-refusal.js.map +0 -1
- package/dist/api/session-refresh.d.ts +0 -18
- package/dist/api/session-refresh.js +0 -37
- package/dist/api/session-refresh.js.map +0 -1
- package/dist/demo-content-mock.d.ts +0 -22
- package/dist/demo-content-mock.js +0 -70
- package/dist/demo-content-mock.js.map +0 -1
- package/dist/demo-content-poller.d.ts +0 -33
- package/dist/demo-content-poller.js +0 -80
- package/dist/demo-content-poller.js.map +0 -1
- package/dist/demo-content.d.ts +0 -43
- package/dist/demo-content.js +0 -78
- package/dist/demo-content.js.map +0 -1
- package/dist/stored-token.d.ts +0 -1
- package/dist/stored-token.js +0 -11
- package/dist/stored-token.js.map +0 -1
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Olive's merchant API answers in its own shapes, which Box does not own, so these read them the way the
|
|
3
|
-
* merchant's own POS page does: tolerant of a `data` wrapper, of a list under one of several names, and of
|
|
4
|
-
* a field under one of several keys. Pure, so the guesses are tested rather than discovered on a live account.
|
|
5
|
-
*/
|
|
6
|
-
export type OliveResource = Record<string, unknown>;
|
|
7
|
-
/** The object an answer carries: its `data` when that is an object, else the answer itself. */
|
|
8
|
-
export declare function oliveObject(body: unknown): OliveResource;
|
|
9
|
-
/** The list an answer carries, wherever Olive put it; empty when there is none. */
|
|
10
|
-
export declare function oliveList(body: unknown): OliveResource[];
|
|
11
|
-
/** The first of these keys that holds text or a number, as text; null when none does. */
|
|
12
|
-
export declare function oliveText(r: OliveResource, ...keys: string[]): string | null;
|
|
13
|
-
export interface OliveOutlet {
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
address: string | null;
|
|
17
|
-
contact: string | null;
|
|
18
|
-
terminalCount: number | null;
|
|
19
|
-
active: boolean;
|
|
20
|
-
createdAt: string | null;
|
|
21
|
-
}
|
|
22
|
-
/** An outlet row; an entry with no id is left out, as the merchant's page does. */
|
|
23
|
-
export declare function oliveOutlets(body: unknown): OliveOutlet[];
|
|
24
|
-
export interface OliveBalance {
|
|
25
|
-
/** The balance as Olive sent it, in kobo (minor units); null when the answer has none. */
|
|
26
|
-
balanceMinor: number | null;
|
|
27
|
-
accountNumber: string | null;
|
|
28
|
-
accountName: string | null;
|
|
29
|
-
}
|
|
30
|
-
export declare function oliveBalance(body: unknown): OliveBalance;
|
|
31
|
-
/** Whether a ledger line is money in or out, by the words Olive uses; null when it says neither. */
|
|
32
|
-
export declare function directionOf(t: OliveResource): 'in' | 'out' | null;
|
|
33
|
-
export interface OliveTransaction {
|
|
34
|
-
id: string;
|
|
35
|
-
at: string | null;
|
|
36
|
-
summary: string;
|
|
37
|
-
/** Kobo (minor units), as Olive sends it. */
|
|
38
|
-
amountMinor: number | null;
|
|
39
|
-
direction: 'in' | 'out' | null;
|
|
40
|
-
status: string | null;
|
|
41
|
-
reference: string | null;
|
|
42
|
-
}
|
|
43
|
-
/** Account ledger lines, in the order Olive sent them. Amounts are kobo. */
|
|
44
|
-
export declare function oliveTransactions(body: unknown): OliveTransaction[];
|
package/dist/admin-pos-olive.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
const isObject = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
2
|
-
const LIST_KEYS = [
|
|
3
|
-
"items",
|
|
4
|
-
"results",
|
|
5
|
-
"transactions",
|
|
6
|
-
"branches",
|
|
7
|
-
"terminals",
|
|
8
|
-
"data"
|
|
9
|
-
];
|
|
10
|
-
function oliveObject(body) {
|
|
11
|
-
if (!isObject(body)) return {};
|
|
12
|
-
return isObject(body.data) ? body.data : body;
|
|
13
|
-
}
|
|
14
|
-
function oliveList(body) {
|
|
15
|
-
if (Array.isArray(body)) return body.filter(isObject);
|
|
16
|
-
const root = oliveObject(body);
|
|
17
|
-
for (const key of LIST_KEYS) {
|
|
18
|
-
const found = root[key];
|
|
19
|
-
if (Array.isArray(found)) return found.filter(isObject);
|
|
20
|
-
}
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
function oliveText(r, ...keys) {
|
|
24
|
-
for (const key of keys) {
|
|
25
|
-
const v = r[key];
|
|
26
|
-
if (typeof v === "number") return String(v);
|
|
27
|
-
if (typeof v === "string" && v.trim()) return v.trim();
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
function oliveOutlets(body) {
|
|
32
|
-
return oliveList(body).flatMap((o, i) => {
|
|
33
|
-
const id = oliveText(o, "branchId", "id");
|
|
34
|
-
if (!id) return [];
|
|
35
|
-
const contact = [
|
|
36
|
-
oliveText(o, "contactFullName"),
|
|
37
|
-
oliveText(o, "contactNumber")
|
|
38
|
-
].filter(Boolean).join(" \xB7 ");
|
|
39
|
-
const count = Number(o.terminalCount);
|
|
40
|
-
return [
|
|
41
|
-
{
|
|
42
|
-
id,
|
|
43
|
-
name: oliveText(o, "name", "branchName", "outletName", "label") ?? `Outlet ${i + 1}`,
|
|
44
|
-
address: oliveText(o, "address"),
|
|
45
|
-
contact: contact || null,
|
|
46
|
-
terminalCount: o.terminalCount == null || !Number.isFinite(count) ? null : count,
|
|
47
|
-
active: o.isActive !== false,
|
|
48
|
-
createdAt: oliveText(o, "createdAt")
|
|
49
|
-
}
|
|
50
|
-
];
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
function oliveBalance(body) {
|
|
54
|
-
const a = oliveObject(body);
|
|
55
|
-
const raw = a.availableBalance ?? a.balance;
|
|
56
|
-
const n = typeof raw === "string" && raw.trim() === "" ? Number.NaN : Number(raw);
|
|
57
|
-
return {
|
|
58
|
-
balanceMinor: raw == null || !Number.isFinite(n) ? null : n,
|
|
59
|
-
accountNumber: oliveText(a, "accountNumber"),
|
|
60
|
-
accountName: oliveText(a, "accountName", "name")
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function directionOf(t) {
|
|
64
|
-
const raw = (oliveText(t, "direction", "type", "transactionType", "entryType") ?? "").toLowerCase();
|
|
65
|
-
if (/\b(credit|inflow|deposit|received|collection)\b/.test(raw)) return "in";
|
|
66
|
-
if (/\b(debit|outflow|withdrawal|payout|transfer)\b/.test(raw)) return "out";
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
function oliveTransactions(body) {
|
|
70
|
-
return oliveList(body).map((t, i) => {
|
|
71
|
-
const raw = t.amount ?? t.transactionAmount;
|
|
72
|
-
const n = Number(raw);
|
|
73
|
-
return {
|
|
74
|
-
id: oliveText(t, "id", "transactionId", "reference") ?? `row-${i}`,
|
|
75
|
-
at: oliveText(t, "createdAt", "transactionDate", "date"),
|
|
76
|
-
summary: oliveText(t, "narration", "description", "type") ?? "Transaction",
|
|
77
|
-
amountMinor: raw == null || raw === "" || !Number.isFinite(n) ? null : n,
|
|
78
|
-
direction: directionOf(t),
|
|
79
|
-
status: oliveText(t, "status"),
|
|
80
|
-
reference: oliveText(t, "reference", "transactionReference")
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
export {
|
|
85
|
-
directionOf,
|
|
86
|
-
oliveBalance,
|
|
87
|
-
oliveList,
|
|
88
|
-
oliveObject,
|
|
89
|
-
oliveOutlets,
|
|
90
|
-
oliveText,
|
|
91
|
-
oliveTransactions
|
|
92
|
-
};
|
|
93
|
-
//# sourceMappingURL=admin-pos-olive.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/admin-pos-olive.ts"],"sourcesContent":["/**\n * Olive's merchant API answers in its own shapes, which Box does not own, so these read them the way the\n * merchant's own POS page does: tolerant of a `data` wrapper, of a list under one of several names, and of\n * a field under one of several keys. Pure, so the guesses are tested rather than discovered on a live account.\n */\n\nexport type OliveResource = Record<string, unknown>;\n\nconst isObject = (v: unknown): v is OliveResource =>\n Boolean(v) && typeof v === 'object' && !Array.isArray(v);\n\nconst LIST_KEYS = [\n 'items',\n 'results',\n 'transactions',\n 'branches',\n 'terminals',\n 'data',\n];\n\n/** The object an answer carries: its `data` when that is an object, else the answer itself. */\nexport function oliveObject(body: unknown): OliveResource {\n if (!isObject(body)) return {};\n\n return isObject(body.data) ? body.data : body;\n}\n\n/** The list an answer carries, wherever Olive put it; empty when there is none. */\nexport function oliveList(body: unknown): OliveResource[] {\n if (Array.isArray(body)) return body.filter(isObject);\n\n const root = oliveObject(body);\n\n for (const key of LIST_KEYS) {\n const found = root[key];\n\n if (Array.isArray(found)) return found.filter(isObject);\n }\n\n return [];\n}\n\n/** The first of these keys that holds text or a number, as text; null when none does. */\nexport function oliveText(r: OliveResource, ...keys: string[]): string | null {\n for (const key of keys) {\n const v = r[key];\n\n if (typeof v === 'number') return String(v);\n\n if (typeof v === 'string' && v.trim()) return v.trim();\n }\n\n return null;\n}\n\nexport interface OliveOutlet {\n id: string;\n name: string;\n address: string | null;\n contact: string | null;\n terminalCount: number | null;\n active: boolean;\n createdAt: string | null;\n}\n\n/** An outlet row; an entry with no id is left out, as the merchant's page does. */\nexport function oliveOutlets(body: unknown): OliveOutlet[] {\n return oliveList(body).flatMap((o, i) => {\n const id = oliveText(o, 'branchId', 'id');\n\n if (!id) return [];\n\n const contact = [\n oliveText(o, 'contactFullName'),\n oliveText(o, 'contactNumber'),\n ]\n .filter(Boolean)\n .join(' · ');\n const count = Number(o.terminalCount);\n\n return [\n {\n id,\n name:\n oliveText(o, 'name', 'branchName', 'outletName', 'label') ??\n `Outlet ${i + 1}`,\n address: oliveText(o, 'address'),\n contact: contact || null,\n terminalCount:\n o.terminalCount == null || !Number.isFinite(count) ? null : count,\n active: o.isActive !== false,\n createdAt: oliveText(o, 'createdAt'),\n },\n ];\n });\n}\n\nexport interface OliveBalance {\n /** The balance as Olive sent it, in kobo (minor units); null when the answer has none. */\n balanceMinor: number | null;\n accountNumber: string | null;\n accountName: string | null;\n}\n\nexport function oliveBalance(body: unknown): OliveBalance {\n const a = oliveObject(body);\n const raw = a.availableBalance ?? a.balance;\n const n =\n typeof raw === 'string' && raw.trim() === '' ? Number.NaN : Number(raw);\n\n return {\n balanceMinor: raw == null || !Number.isFinite(n) ? null : n,\n accountNumber: oliveText(a, 'accountNumber'),\n accountName: oliveText(a, 'accountName', 'name'),\n };\n}\n\n/** Whether a ledger line is money in or out, by the words Olive uses; null when it says neither. */\nexport function directionOf(t: OliveResource): 'in' | 'out' | null {\n const raw = (\n oliveText(t, 'direction', 'type', 'transactionType', 'entryType') ?? ''\n ).toLowerCase();\n\n if (/\\b(credit|inflow|deposit|received|collection)\\b/.test(raw)) return 'in';\n\n if (/\\b(debit|outflow|withdrawal|payout|transfer)\\b/.test(raw)) return 'out';\n\n return null;\n}\n\nexport interface OliveTransaction {\n id: string;\n at: string | null;\n summary: string;\n /** Kobo (minor units), as Olive sends it. */\n amountMinor: number | null;\n direction: 'in' | 'out' | null;\n status: string | null;\n reference: string | null;\n}\n\n/** Account ledger lines, in the order Olive sent them. Amounts are kobo. */\nexport function oliveTransactions(body: unknown): OliveTransaction[] {\n return oliveList(body).map((t, i) => {\n const raw = t.amount ?? t.transactionAmount;\n const n = Number(raw);\n\n return {\n id: oliveText(t, 'id', 'transactionId', 'reference') ?? `row-${i}`,\n at: oliveText(t, 'createdAt', 'transactionDate', 'date'),\n summary:\n oliveText(t, 'narration', 'description', 'type') ?? 'Transaction',\n amountMinor: raw == null || raw === '' || !Number.isFinite(n) ? null : n,\n direction: directionOf(t),\n status: oliveText(t, 'status'),\n reference: oliveText(t, 'reference', 'transactionReference'),\n };\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Demo content: sample products, collections, pages and pictures a merchant
|
|
3
|
-
* can start a store with, clear in one go, or apply again later.
|
|
4
|
-
*
|
|
5
|
-
* Contract: api-next `DEMO_CONTENT_API.md`. Every call is the current tenant's
|
|
6
|
-
* and needs an owner or admin; anyone else is refused by the server, and the
|
|
7
|
-
* screens hide the actions before that.
|
|
8
|
-
*/
|
|
9
|
-
export type DemoContentState = 'none' | 'applying' | 'applied' | 'clearing' | 'failed';
|
|
10
|
-
export interface DemoContentPack {
|
|
11
|
-
id: string;
|
|
12
|
-
template: string;
|
|
13
|
-
/** Absent until the pack has been read, as is `hash` (empty while the job starts). */
|
|
14
|
-
version?: string;
|
|
15
|
-
audience: string;
|
|
16
|
-
hash: string;
|
|
17
|
-
}
|
|
18
|
-
export interface DemoContentProgress {
|
|
19
|
-
phase: string;
|
|
20
|
-
done: number;
|
|
21
|
-
total: number;
|
|
22
|
-
}
|
|
23
|
-
export interface DemoContentCounts {
|
|
24
|
-
products: number;
|
|
25
|
-
collections: number;
|
|
26
|
-
pages: number;
|
|
27
|
-
media: number;
|
|
28
|
-
}
|
|
29
|
-
export interface DemoContentStatus {
|
|
30
|
-
state: DemoContentState;
|
|
31
|
-
pack?: DemoContentPack;
|
|
32
|
-
progress?: DemoContentProgress;
|
|
33
|
-
counts?: DemoContentCounts;
|
|
34
|
-
startedAt?: string;
|
|
35
|
-
finishedAt?: string;
|
|
36
|
-
/** Pages the server left alone because the merchant had already edited them. */
|
|
37
|
-
skippedPages?: string[];
|
|
38
|
-
/** Which job is running, or which one failed. */
|
|
39
|
-
job?: 'apply' | 'clear';
|
|
40
|
-
error?: {
|
|
41
|
-
code: string;
|
|
42
|
-
message: string;
|
|
43
|
-
};
|
|
44
|
-
/** A clear that left uploaded pictures in the media library. */
|
|
45
|
-
orphanedMedia?: {
|
|
46
|
-
count: number;
|
|
47
|
-
clearedAt?: string;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/** Whether the tenant's template has a pack this caller may apply. */
|
|
51
|
-
export interface DemoContentAvailability {
|
|
52
|
-
available: boolean;
|
|
53
|
-
template?: string;
|
|
54
|
-
/** The current status state, so a screen can hide the offer once content is there. */
|
|
55
|
-
state?: DemoContentState;
|
|
56
|
-
/** Why there is nothing to offer: no_pack, audience_not_permitted, catalogue_unreachable, template_unknown. */
|
|
57
|
-
reason?: string;
|
|
58
|
-
pack?: {
|
|
59
|
-
id: string;
|
|
60
|
-
name?: string;
|
|
61
|
-
description?: string;
|
|
62
|
-
version?: string;
|
|
63
|
-
audience?: string;
|
|
64
|
-
locale?: string;
|
|
65
|
-
hash?: string;
|
|
66
|
-
};
|
|
67
|
-
/** What the pack holds. Only what the server gave is ever shown. */
|
|
68
|
-
summary?: {
|
|
69
|
-
products?: number;
|
|
70
|
-
collections?: number;
|
|
71
|
-
contentItems?: number;
|
|
72
|
-
pages?: number;
|
|
73
|
-
images?: number;
|
|
74
|
-
shipping?: boolean;
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
export interface DemoContentApplyInput {
|
|
78
|
-
template?: string;
|
|
79
|
-
dryRun?: boolean;
|
|
80
|
-
}
|
|
81
|
-
/** The calls the screens make, so a test or a preview can stand in for the server. */
|
|
82
|
-
export interface DemoContentClient {
|
|
83
|
-
status(): Promise<DemoContentStatus>;
|
|
84
|
-
available(): Promise<DemoContentAvailability>;
|
|
85
|
-
apply(input?: DemoContentApplyInput): Promise<{
|
|
86
|
-
jobId: string;
|
|
87
|
-
}>;
|
|
88
|
-
clear(options: {
|
|
89
|
-
keepMedia: boolean;
|
|
90
|
-
}): Promise<void>;
|
|
91
|
-
adopt(input: {
|
|
92
|
-
productIds?: string[];
|
|
93
|
-
collectionIds?: string[];
|
|
94
|
-
contentItemIds?: string[];
|
|
95
|
-
all?: boolean;
|
|
96
|
-
}): Promise<void>;
|
|
97
|
-
}
|
|
98
|
-
export declare function createDemoContentApi(tenantSlug: string): DemoContentClient;
|
|
99
|
-
/**
|
|
100
|
-
* The contract's error `code` (`job_in_progress`, `not_owner_or_admin`,
|
|
101
|
-
* `no_demo_content`, ...) when the server sent one. The body is
|
|
102
|
-
* `{ statusCode, code, message, details? }` and `ApiRequestError.details` holds it.
|
|
103
|
-
*/
|
|
104
|
-
export declare function apiErrorCode(error: unknown): string | undefined;
|
package/dist/api/demo-content.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { apiFetch, ApiRequestError } from "./core";
|
|
2
|
-
const BASE = "/tenants/current/demo-content";
|
|
3
|
-
function createDemoContentApi(tenantSlug) {
|
|
4
|
-
return {
|
|
5
|
-
status: () => apiFetch(BASE, { tenantSlug }),
|
|
6
|
-
available: () => apiFetch(`${BASE}/available`, { tenantSlug }),
|
|
7
|
-
apply: (input) => apiFetch(BASE, {
|
|
8
|
-
method: "POST",
|
|
9
|
-
tenantSlug,
|
|
10
|
-
body: JSON.stringify(input ?? {})
|
|
11
|
-
}),
|
|
12
|
-
clear: async ({ keepMedia }) => {
|
|
13
|
-
await apiFetch(`${BASE}?keepMedia=${keepMedia ? "true" : "false"}`, {
|
|
14
|
-
method: "DELETE",
|
|
15
|
-
tenantSlug
|
|
16
|
-
});
|
|
17
|
-
},
|
|
18
|
-
adopt: async (input) => {
|
|
19
|
-
await apiFetch(`${BASE}/adopt`, {
|
|
20
|
-
method: "POST",
|
|
21
|
-
tenantSlug,
|
|
22
|
-
body: JSON.stringify(input)
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function apiErrorCode(error) {
|
|
28
|
-
if (!(error instanceof ApiRequestError)) return void 0;
|
|
29
|
-
const code = error.details?.code;
|
|
30
|
-
return typeof code === "string" ? code : void 0;
|
|
31
|
-
}
|
|
32
|
-
export {
|
|
33
|
-
apiErrorCode,
|
|
34
|
-
createDemoContentApi
|
|
35
|
-
};
|
|
36
|
-
//# sourceMappingURL=demo-content.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/demo-content.ts"],"sourcesContent":["import { apiFetch, ApiRequestError } from './core';\n\n/**\n * Demo content: sample products, collections, pages and pictures a merchant\n * can start a store with, clear in one go, or apply again later.\n *\n * Contract: api-next `DEMO_CONTENT_API.md`. Every call is the current tenant's\n * and needs an owner or admin; anyone else is refused by the server, and the\n * screens hide the actions before that.\n */\nexport type DemoContentState =\n | 'none'\n | 'applying'\n | 'applied'\n | 'clearing'\n | 'failed';\n\nexport interface DemoContentPack {\n id: string;\n template: string;\n /** Absent until the pack has been read, as is `hash` (empty while the job starts). */\n version?: string;\n audience: string;\n hash: string;\n}\n\nexport interface DemoContentProgress {\n phase: string;\n done: number;\n total: number;\n}\n\nexport interface DemoContentCounts {\n products: number;\n collections: number;\n pages: number;\n media: number;\n}\n\nexport interface DemoContentStatus {\n state: DemoContentState;\n pack?: DemoContentPack;\n progress?: DemoContentProgress;\n counts?: DemoContentCounts;\n startedAt?: string;\n finishedAt?: string;\n /** Pages the server left alone because the merchant had already edited them. */\n skippedPages?: string[];\n /** Which job is running, or which one failed. */\n job?: 'apply' | 'clear';\n error?: { code: string; message: string };\n /** A clear that left uploaded pictures in the media library. */\n orphanedMedia?: { count: number; clearedAt?: string };\n}\n\n/** Whether the tenant's template has a pack this caller may apply. */\nexport interface DemoContentAvailability {\n available: boolean;\n template?: string;\n /** The current status state, so a screen can hide the offer once content is there. */\n state?: DemoContentState;\n /** Why there is nothing to offer: no_pack, audience_not_permitted, catalogue_unreachable, template_unknown. */\n reason?: string;\n pack?: {\n id: string;\n name?: string;\n description?: string;\n version?: string;\n audience?: string;\n locale?: string;\n hash?: string;\n };\n /** What the pack holds. Only what the server gave is ever shown. */\n summary?: {\n products?: number;\n collections?: number;\n contentItems?: number;\n pages?: number;\n images?: number;\n shipping?: boolean;\n };\n}\n\nexport interface DemoContentApplyInput {\n template?: string;\n dryRun?: boolean;\n}\n\n/** The calls the screens make, so a test or a preview can stand in for the server. */\nexport interface DemoContentClient {\n status(): Promise<DemoContentStatus>;\n available(): Promise<DemoContentAvailability>;\n apply(input?: DemoContentApplyInput): Promise<{ jobId: string }>;\n clear(options: { keepMedia: boolean }): Promise<void>;\n adopt(input: {\n productIds?: string[];\n collectionIds?: string[];\n contentItemIds?: string[];\n all?: boolean;\n }): Promise<void>;\n}\n\nconst BASE = '/tenants/current/demo-content';\n\nexport function createDemoContentApi(tenantSlug: string): DemoContentClient {\n return {\n status: () => apiFetch<DemoContentStatus>(BASE, { tenantSlug }),\n available: () =>\n apiFetch<DemoContentAvailability>(`${BASE}/available`, { tenantSlug }),\n apply: (input) =>\n apiFetch<{ jobId: string }>(BASE, {\n method: 'POST',\n tenantSlug,\n body: JSON.stringify(input ?? {}),\n }),\n clear: async ({ keepMedia }) => {\n await apiFetch<unknown>(`${BASE}?keepMedia=${keepMedia ? 'true' : 'false'}`, {\n method: 'DELETE',\n tenantSlug,\n });\n },\n adopt: async (input) => {\n await apiFetch<unknown>(`${BASE}/adopt`, {\n method: 'POST',\n tenantSlug,\n body: JSON.stringify(input),\n });\n },\n };\n}\n\n/**\n * The contract's error `code` (`job_in_progress`, `not_owner_or_admin`,\n * `no_demo_content`, ...) when the server sent one. The body is\n * `{ statusCode, code, message, details? }` and `ApiRequestError.details` holds it.\n */\nexport function apiErrorCode(error: unknown): string | undefined {\n if (!(error instanceof ApiRequestError)) return undefined;\n const code = (error.details as { code?: unknown } | undefined)?.code;\n return typeof code === 'string' ? code : undefined;\n}\n"],"mappings":"AAAA,SAAS,UAAU,uBAAuB;AAsG1C,MAAM,OAAO;AAEN,SAAS,qBAAqB,YAAuC;AAC1E,SAAO;AAAA,IACL,QAAQ,MAAM,SAA4B,MAAM,EAAE,WAAW,CAAC;AAAA,IAC9D,WAAW,MACT,SAAkC,GAAG,IAAI,cAAc,EAAE,WAAW,CAAC;AAAA,IACvE,OAAO,CAAC,UACN,SAA4B,MAAM;AAAA,MAChC,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,SAAS,CAAC,CAAC;AAAA,IAClC,CAAC;AAAA,IACH,OAAO,OAAO,EAAE,UAAU,MAAM;AAC9B,YAAM,SAAkB,GAAG,IAAI,cAAc,YAAY,SAAS,OAAO,IAAI;AAAA,QAC3E,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,OAAO,OAAO,UAAU;AACtB,YAAM,SAAkB,GAAG,IAAI,UAAU;AAAA,QACvC,QAAQ;AAAA,QACR;AAAA,QACA,MAAM,KAAK,UAAU,KAAK;AAAA,MAC5B,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAOO,SAAS,aAAa,OAAoC;AAC/D,MAAI,EAAE,iBAAiB,iBAAkB,QAAO;AAChD,QAAM,OAAQ,MAAM,SAA4C;AAChE,SAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;","names":[]}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A 402 from Olive's subscription guard: `{ code, feature, requiredPlan }` with
|
|
3
|
-
* `code` one of the four below. It is the engine saying "this plan does not
|
|
4
|
-
* allow that", not "your subscription has lapsed", so it must not send the
|
|
5
|
-
* merchant to billing: the dashboard shows an upgrade prompt where they are and
|
|
6
|
-
* the merchant stays in what they were doing.
|
|
7
|
-
*
|
|
8
|
-
* The event name is also written out in `@withpotter/olive-subscription`
|
|
9
|
-
* (`PlanRefusalModal`), which listens for it and does not import this package.
|
|
10
|
-
*/
|
|
11
|
-
export declare const PLAN_REFUSAL_EVENT = "olive:plan-refusal";
|
|
12
|
-
export declare const PLAN_REFUSAL_CODES: readonly ["LIMIT_REACHED", "FEATURE_LOCKED", "SUBSCRIPTION_REQUIRED", "VIEW_ONLY"];
|
|
13
|
-
export type PlanRefusalCode = (typeof PLAN_REFUSAL_CODES)[number];
|
|
14
|
-
export interface PlanRefusal {
|
|
15
|
-
code: PlanRefusalCode;
|
|
16
|
-
feature: string | null;
|
|
17
|
-
requiredPlan: string | null;
|
|
18
|
-
}
|
|
19
|
-
/** The refusal a 402 body carries, or null when it is some other 402. */
|
|
20
|
-
export declare function planRefusal(body: unknown): PlanRefusal | null;
|
|
21
|
-
export declare function announcePlanRefusal(refusal: PlanRefusal): void;
|
package/dist/api/plan-refusal.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const PLAN_REFUSAL_EVENT = "olive:plan-refusal";
|
|
2
|
-
const PLAN_REFUSAL_CODES = [
|
|
3
|
-
"LIMIT_REACHED",
|
|
4
|
-
"FEATURE_LOCKED",
|
|
5
|
-
"SUBSCRIPTION_REQUIRED",
|
|
6
|
-
"VIEW_ONLY"
|
|
7
|
-
];
|
|
8
|
-
function planRefusal(body) {
|
|
9
|
-
if (!body || typeof body !== "object") return null;
|
|
10
|
-
const { code, feature, requiredPlan } = body;
|
|
11
|
-
if (!PLAN_REFUSAL_CODES.some((c) => c === code)) return null;
|
|
12
|
-
return {
|
|
13
|
-
code,
|
|
14
|
-
feature: typeof feature === "string" ? feature : null,
|
|
15
|
-
requiredPlan: typeof requiredPlan === "string" ? requiredPlan : null
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function announcePlanRefusal(refusal) {
|
|
19
|
-
if (typeof window === "undefined") return;
|
|
20
|
-
window.dispatchEvent(new CustomEvent(PLAN_REFUSAL_EVENT, { detail: refusal }));
|
|
21
|
-
}
|
|
22
|
-
export {
|
|
23
|
-
PLAN_REFUSAL_CODES,
|
|
24
|
-
PLAN_REFUSAL_EVENT,
|
|
25
|
-
announcePlanRefusal,
|
|
26
|
-
planRefusal
|
|
27
|
-
};
|
|
28
|
-
//# sourceMappingURL=plan-refusal.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/plan-refusal.ts"],"sourcesContent":["/**\n * A 402 from Olive's subscription guard: `{ code, feature, requiredPlan }` with\n * `code` one of the four below. It is the engine saying \"this plan does not\n * allow that\", not \"your subscription has lapsed\", so it must not send the\n * merchant to billing: the dashboard shows an upgrade prompt where they are and\n * the merchant stays in what they were doing.\n *\n * The event name is also written out in `@withpotter/olive-subscription`\n * (`PlanRefusalModal`), which listens for it and does not import this package.\n */\nexport const PLAN_REFUSAL_EVENT = \"olive:plan-refusal\";\n\nexport const PLAN_REFUSAL_CODES = [\n \"LIMIT_REACHED\",\n \"FEATURE_LOCKED\",\n \"SUBSCRIPTION_REQUIRED\",\n \"VIEW_ONLY\",\n] as const;\n\nexport type PlanRefusalCode = (typeof PLAN_REFUSAL_CODES)[number];\n\nexport interface PlanRefusal {\n code: PlanRefusalCode;\n feature: string | null;\n requiredPlan: string | null;\n}\n\n/** The refusal a 402 body carries, or null when it is some other 402. */\nexport function planRefusal(body: unknown): PlanRefusal | null {\n if (!body || typeof body !== \"object\") return null;\n\n const { code, feature, requiredPlan } = body as {\n code?: unknown;\n feature?: unknown;\n requiredPlan?: unknown;\n };\n\n if (!PLAN_REFUSAL_CODES.some((c) => c === code)) return null;\n\n return {\n code: code as PlanRefusalCode,\n feature: typeof feature === \"string\" ? feature : null,\n requiredPlan: typeof requiredPlan === \"string\" ? requiredPlan : null,\n };\n}\n\nexport function announcePlanRefusal(refusal: PlanRefusal): void {\n if (typeof window === \"undefined\") return;\n\n window.dispatchEvent(new CustomEvent(PLAN_REFUSAL_EVENT, { detail: refusal }));\n}\n"],"mappings":"AAUO,MAAM,qBAAqB;AAE3B,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,YAAY,MAAmC;AAC7D,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAE9C,QAAM,EAAE,MAAM,SAAS,aAAa,IAAI;AAMxC,MAAI,CAAC,mBAAmB,KAAK,CAAC,MAAM,MAAM,IAAI,EAAG,QAAO;AAExD,SAAO;AAAA,IACL;AAAA,IACA,SAAS,OAAO,YAAY,WAAW,UAAU;AAAA,IACjD,cAAc,OAAO,iBAAiB,WAAW,eAAe;AAAA,EAClE;AACF;AAEO,SAAS,oBAAoB,SAA4B;AAC9D,MAAI,OAAO,WAAW,YAAa;AAEnC,SAAO,cAAc,IAAI,YAAY,oBAAoB,EAAE,QAAQ,QAAQ,CAAC,CAAC;AAC/E;","names":[]}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Renewing the session when its short-lived access token has run out.
|
|
3
|
-
*
|
|
4
|
-
* The engine keeps the session in HttpOnly cookies: a 15-minute access token
|
|
5
|
-
* and a rotating refresh token. This code cannot read either; it only asks
|
|
6
|
-
* `POST /accounts/refresh` to swap them when a request comes back "expired".
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Whether a 401 from `url` could be an expired access token. Called once with
|
|
10
|
-
* the URL alone (to skip the body read) and again with the body.
|
|
11
|
-
*/
|
|
12
|
-
export declare function wantsRefresh(url: string, body?: unknown): boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Ask the engine for a new session. Every request that failed together shares
|
|
15
|
-
* one call. False when it cannot be renewed (refused, or an engine without the
|
|
16
|
-
* route), so the caller falls back to ending the session.
|
|
17
|
-
*/
|
|
18
|
-
export declare function refreshSession(apiBase: string): Promise<boolean>;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const REFRESH_PATH = "/accounts/refresh";
|
|
2
|
-
const NEVER_REFRESH = [REFRESH_PATH, "/accounts/login", "/accounts/logout"];
|
|
3
|
-
const ADMIN_API = "/platform-admin/";
|
|
4
|
-
const EXPIRED = /expired|no authentication token/i;
|
|
5
|
-
const IDLE = /session_idle/i;
|
|
6
|
-
let inFlight = null;
|
|
7
|
-
function wantsRefresh(url, body) {
|
|
8
|
-
const path = new URL(url, "http://localhost").pathname;
|
|
9
|
-
if (path.includes(ADMIN_API)) return false;
|
|
10
|
-
if (NEVER_REFRESH.some((never) => path.endsWith(never))) return false;
|
|
11
|
-
if (body === void 0) return true;
|
|
12
|
-
const { message, code } = body ?? {};
|
|
13
|
-
const text = [code, ...Array.isArray(message) ? message : [message]].filter((part) => typeof part === "string").join(" ");
|
|
14
|
-
return EXPIRED.test(text) && !IDLE.test(text);
|
|
15
|
-
}
|
|
16
|
-
function refreshSession(apiBase) {
|
|
17
|
-
inFlight ??= renew(apiBase).finally(() => {
|
|
18
|
-
inFlight = null;
|
|
19
|
-
});
|
|
20
|
-
return inFlight;
|
|
21
|
-
}
|
|
22
|
-
async function renew(apiBase) {
|
|
23
|
-
try {
|
|
24
|
-
const res = await fetch(`${apiBase}${REFRESH_PATH}`, {
|
|
25
|
-
method: "POST",
|
|
26
|
-
credentials: "include"
|
|
27
|
-
});
|
|
28
|
-
return res.ok;
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export {
|
|
34
|
-
refreshSession,
|
|
35
|
-
wantsRefresh
|
|
36
|
-
};
|
|
37
|
-
//# sourceMappingURL=session-refresh.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/session-refresh.ts"],"sourcesContent":["/**\n * Renewing the session when its short-lived access token has run out.\n *\n * The engine keeps the session in HttpOnly cookies: a 15-minute access token\n * and a rotating refresh token. This code cannot read either; it only asks\n * `POST /accounts/refresh` to swap them when a request comes back \"expired\".\n */\n\nconst REFRESH_PATH = '/accounts/refresh';\n/** Calls whose 401 means something else (wrong details, nothing to end). */\nconst NEVER_REFRESH = [REFRESH_PATH, '/accounts/login', '/accounts/logout'];\nconst ADMIN_API = '/platform-admin/';\nconst EXPIRED = /expired|no authentication token/i;\nconst IDLE = /session_idle/i;\n\nlet inFlight: Promise<boolean> | null = null;\n\n/**\n * Whether a 401 from `url` could be an expired access token. Called once with\n * the URL alone (to skip the body read) and again with the body.\n */\nexport function wantsRefresh(url: string, body?: unknown): boolean {\n const path = new URL(url, 'http://localhost').pathname;\n if (path.includes(ADMIN_API)) return false;\n if (NEVER_REFRESH.some((never) => path.endsWith(never))) return false;\n if (body === undefined) return true;\n\n const { message, code } = (body ?? {}) as { message?: unknown; code?: unknown };\n const text = [code, ...(Array.isArray(message) ? message : [message])]\n .filter((part): part is string => typeof part === 'string')\n .join(' ');\n return EXPIRED.test(text) && !IDLE.test(text);\n}\n\n/**\n * Ask the engine for a new session. Every request that failed together shares\n * one call. False when it cannot be renewed (refused, or an engine without the\n * route), so the caller falls back to ending the session.\n */\nexport function refreshSession(apiBase: string): Promise<boolean> {\n inFlight ??= renew(apiBase).finally(() => {\n inFlight = null;\n });\n return inFlight;\n}\n\nasync function renew(apiBase: string): Promise<boolean> {\n try {\n const res = await fetch(`${apiBase}${REFRESH_PATH}`, {\n method: 'POST',\n credentials: 'include',\n });\n return res.ok;\n } catch {\n return false;\n }\n}\n"],"mappings":"AAQA,MAAM,eAAe;AAErB,MAAM,gBAAgB,CAAC,cAAc,mBAAmB,kBAAkB;AAC1E,MAAM,YAAY;AAClB,MAAM,UAAU;AAChB,MAAM,OAAO;AAEb,IAAI,WAAoC;AAMjC,SAAS,aAAa,KAAa,MAAyB;AACjE,QAAM,OAAO,IAAI,IAAI,KAAK,kBAAkB,EAAE;AAC9C,MAAI,KAAK,SAAS,SAAS,EAAG,QAAO;AACrC,MAAI,cAAc,KAAK,CAAC,UAAU,KAAK,SAAS,KAAK,CAAC,EAAG,QAAO;AAChE,MAAI,SAAS,OAAW,QAAO;AAE/B,QAAM,EAAE,SAAS,KAAK,IAAK,QAAQ,CAAC;AACpC,QAAM,OAAO,CAAC,MAAM,GAAI,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO,CAAE,EAClE,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,EACzD,KAAK,GAAG;AACX,SAAO,QAAQ,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI;AAC9C;AAOO,SAAS,eAAe,SAAmC;AAChE,eAAa,MAAM,OAAO,EAAE,QAAQ,MAAM;AACxC,eAAW;AAAA,EACb,CAAC;AACD,SAAO;AACT;AAEA,eAAe,MAAM,SAAmC;AACtD,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,YAAY,IAAI;AAAA,MACnD,QAAQ;AAAA,MACR,aAAa;AAAA,IACf,CAAC;AACD,WAAO,IAAI;AAAA,EACb,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { DemoContentAvailability, DemoContentClient, DemoContentStatus } from './api/demo-content';
|
|
2
|
-
/**
|
|
3
|
-
* A stand-in server for the demo-content screens: tests, and previewing the
|
|
4
|
-
* screens with no backend. It holds the state the real one would, and moves a
|
|
5
|
-
* job along each time the status is read.
|
|
6
|
-
*/
|
|
7
|
-
export interface MockDemoOptions {
|
|
8
|
-
status?: DemoContentStatus;
|
|
9
|
-
availability?: DemoContentAvailability;
|
|
10
|
-
/** Reads before a started job finishes. */
|
|
11
|
-
stepsPerJob?: number;
|
|
12
|
-
/** Make the next calls to these fail with the server's message. */
|
|
13
|
-
failNext?: Partial<Record<keyof DemoContentClient, string>>;
|
|
14
|
-
}
|
|
15
|
-
export declare function createMockDemoClient(options?: MockDemoOptions): {
|
|
16
|
-
client: DemoContentClient;
|
|
17
|
-
calls: {
|
|
18
|
-
name: string;
|
|
19
|
-
args: unknown;
|
|
20
|
-
}[];
|
|
21
|
-
setStatus: (s: DemoContentStatus) => DemoContentStatus;
|
|
22
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
function createMockDemoClient(options = {}) {
|
|
2
|
-
let status = options.status ?? { state: "none" };
|
|
3
|
-
const stepsPerJob = options.stepsPerJob ?? 3;
|
|
4
|
-
const failNext = { ...options.failNext ?? {} };
|
|
5
|
-
let step = 0;
|
|
6
|
-
let nextJob = 1;
|
|
7
|
-
const calls = [];
|
|
8
|
-
const maybeFail = (name) => {
|
|
9
|
-
const message = failNext[name];
|
|
10
|
-
if (message) {
|
|
11
|
-
delete failNext[name];
|
|
12
|
-
throw new Error(message);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
const client = {
|
|
16
|
-
async status() {
|
|
17
|
-
calls.push({ name: "status", args: void 0 });
|
|
18
|
-
maybeFail("status");
|
|
19
|
-
if (status.state === "applying" || status.state === "clearing") {
|
|
20
|
-
step += 1;
|
|
21
|
-
if (step >= stepsPerJob) {
|
|
22
|
-
status = status.state === "applying" ? {
|
|
23
|
-
state: "applied",
|
|
24
|
-
pack: status.pack,
|
|
25
|
-
counts: { products: 12, collections: 3, pages: 4, media: 20 },
|
|
26
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27
|
-
} : { state: "none", finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
28
|
-
} else {
|
|
29
|
-
status = {
|
|
30
|
-
...status,
|
|
31
|
-
progress: { phase: "products", done: step, total: stepsPerJob }
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return status;
|
|
36
|
-
},
|
|
37
|
-
async available() {
|
|
38
|
-
calls.push({ name: "available", args: void 0 });
|
|
39
|
-
maybeFail("available");
|
|
40
|
-
return options.availability ?? { available: true };
|
|
41
|
-
},
|
|
42
|
-
async apply(input) {
|
|
43
|
-
calls.push({ name: "apply", args: input });
|
|
44
|
-
maybeFail("apply");
|
|
45
|
-
step = 0;
|
|
46
|
-
status = {
|
|
47
|
-
state: "applying",
|
|
48
|
-
pack: { id: "pack", template: "demo", version: "1", audience: "all", hash: "h" },
|
|
49
|
-
progress: { phase: "media", done: 0, total: stepsPerJob },
|
|
50
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
51
|
-
};
|
|
52
|
-
return { jobId: `job-${nextJob++}` };
|
|
53
|
-
},
|
|
54
|
-
async clear(args) {
|
|
55
|
-
calls.push({ name: "clear", args });
|
|
56
|
-
maybeFail("clear");
|
|
57
|
-
step = 0;
|
|
58
|
-
status = { ...status, state: "clearing", progress: { phase: "products", done: 0, total: stepsPerJob } };
|
|
59
|
-
},
|
|
60
|
-
async adopt(input) {
|
|
61
|
-
calls.push({ name: "adopt", args: input });
|
|
62
|
-
maybeFail("adopt");
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
return { client, calls, setStatus: (s) => status = s };
|
|
66
|
-
}
|
|
67
|
-
export {
|
|
68
|
-
createMockDemoClient
|
|
69
|
-
};
|
|
70
|
-
//# sourceMappingURL=demo-content-mock.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/demo-content-mock.ts"],"sourcesContent":["import type {\n DemoContentAvailability,\n DemoContentClient,\n DemoContentStatus,\n} from './api/demo-content';\n\n/**\n * A stand-in server for the demo-content screens: tests, and previewing the\n * screens with no backend. It holds the state the real one would, and moves a\n * job along each time the status is read.\n */\nexport interface MockDemoOptions {\n status?: DemoContentStatus;\n availability?: DemoContentAvailability;\n /** Reads before a started job finishes. */\n stepsPerJob?: number;\n /** Make the next calls to these fail with the server's message. */\n failNext?: Partial<Record<keyof DemoContentClient, string>>;\n}\n\nexport function createMockDemoClient(options: MockDemoOptions = {}) {\n let status: DemoContentStatus = options.status ?? { state: 'none' };\n const stepsPerJob = options.stepsPerJob ?? 3;\n const failNext = { ...(options.failNext ?? {}) };\n let step = 0;\n let nextJob = 1;\n const calls: { name: string; args: unknown }[] = [];\n\n const maybeFail = (name: keyof DemoContentClient) => {\n const message = failNext[name];\n if (message) {\n delete failNext[name];\n throw new Error(message);\n }\n };\n\n const client: DemoContentClient = {\n async status() {\n calls.push({ name: 'status', args: undefined });\n maybeFail('status');\n if (status.state === 'applying' || status.state === 'clearing') {\n step += 1;\n if (step >= stepsPerJob) {\n status =\n status.state === 'applying'\n ? {\n state: 'applied',\n pack: status.pack,\n counts: { products: 12, collections: 3, pages: 4, media: 20 },\n finishedAt: new Date().toISOString(),\n }\n : { state: 'none', finishedAt: new Date().toISOString() };\n } else {\n status = {\n ...status,\n progress: { phase: 'products', done: step, total: stepsPerJob },\n };\n }\n }\n return status;\n },\n async available() {\n calls.push({ name: 'available', args: undefined });\n maybeFail('available');\n return options.availability ?? { available: true };\n },\n async apply(input) {\n calls.push({ name: 'apply', args: input });\n maybeFail('apply');\n step = 0;\n status = {\n state: 'applying',\n pack: { id: 'pack', template: 'demo', version: '1', audience: 'all', hash: 'h' },\n progress: { phase: 'media', done: 0, total: stepsPerJob },\n startedAt: new Date().toISOString(),\n };\n return { jobId: `job-${nextJob++}` };\n },\n async clear(args) {\n calls.push({ name: 'clear', args });\n maybeFail('clear');\n step = 0;\n status = { ...status, state: 'clearing', progress: { phase: 'products', done: 0, total: stepsPerJob } };\n },\n async adopt(input) {\n calls.push({ name: 'adopt', args: input });\n maybeFail('adopt');\n },\n };\n return { client, calls, setStatus: (s: DemoContentStatus) => (status = s) };\n}\n"],"mappings":"AAoBO,SAAS,qBAAqB,UAA2B,CAAC,GAAG;AAClE,MAAI,SAA4B,QAAQ,UAAU,EAAE,OAAO,OAAO;AAClE,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,WAAW,EAAE,GAAI,QAAQ,YAAY,CAAC,EAAG;AAC/C,MAAI,OAAO;AACX,MAAI,UAAU;AACd,QAAM,QAA2C,CAAC;AAElD,QAAM,YAAY,CAAC,SAAkC;AACnD,UAAM,UAAU,SAAS,IAAI;AAC7B,QAAI,SAAS;AACX,aAAO,SAAS,IAAI;AACpB,YAAM,IAAI,MAAM,OAAO;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,SAA4B;AAAA,IAChC,MAAM,SAAS;AACb,YAAM,KAAK,EAAE,MAAM,UAAU,MAAM,OAAU,CAAC;AAC9C,gBAAU,QAAQ;AAClB,UAAI,OAAO,UAAU,cAAc,OAAO,UAAU,YAAY;AAC9D,gBAAQ;AACR,YAAI,QAAQ,aAAa;AACvB,mBACE,OAAO,UAAU,aACb;AAAA,YACE,OAAO;AAAA,YACP,MAAM,OAAO;AAAA,YACb,QAAQ,EAAE,UAAU,IAAI,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG;AAAA,YAC5D,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACrC,IACA,EAAE,OAAO,QAAQ,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AAAA,QAC9D,OAAO;AACL,mBAAS;AAAA,YACP,GAAG;AAAA,YACH,UAAU,EAAE,OAAO,YAAY,MAAM,MAAM,OAAO,YAAY;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,MAAM,YAAY;AAChB,YAAM,KAAK,EAAE,MAAM,aAAa,MAAM,OAAU,CAAC;AACjD,gBAAU,WAAW;AACrB,aAAO,QAAQ,gBAAgB,EAAE,WAAW,KAAK;AAAA,IACnD;AAAA,IACA,MAAM,MAAM,OAAO;AACjB,YAAM,KAAK,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC;AACzC,gBAAU,OAAO;AACjB,aAAO;AACP,eAAS;AAAA,QACP,OAAO;AAAA,QACP,MAAM,EAAE,IAAI,QAAQ,UAAU,QAAQ,SAAS,KAAK,UAAU,OAAO,MAAM,IAAI;AAAA,QAC/E,UAAU,EAAE,OAAO,SAAS,MAAM,GAAG,OAAO,YAAY;AAAA,QACxD,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AACA,aAAO,EAAE,OAAO,OAAO,SAAS,GAAG;AAAA,IACrC;AAAA,IACA,MAAM,MAAM,MAAM;AAChB,YAAM,KAAK,EAAE,MAAM,SAAS,KAAK,CAAC;AAClC,gBAAU,OAAO;AACjB,aAAO;AACP,eAAS,EAAE,GAAG,QAAQ,OAAO,YAAY,UAAU,EAAE,OAAO,YAAY,MAAM,GAAG,OAAO,YAAY,EAAE;AAAA,IACxG;AAAA,IACA,MAAM,MAAM,OAAO;AACjB,YAAM,KAAK,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC;AACzC,gBAAU,OAAO;AAAA,IACnB;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,OAAO,WAAW,CAAC,MAA0B,SAAS,EAAG;AAC5E;","names":[]}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { DemoContentStatus } from './api/demo-content';
|
|
2
|
-
/**
|
|
3
|
-
* Keeps asking the server how a demo-content job is going until it stops.
|
|
4
|
-
*
|
|
5
|
-
* No React in it, and the clock and the tab are handed in, so a test moves time
|
|
6
|
-
* by hand. The rules:
|
|
7
|
-
*
|
|
8
|
-
* - Asks once straight away, which is also what picks up a job that was
|
|
9
|
-
* running when the page was closed and opened again.
|
|
10
|
-
* - Keeps asking only while the job is running (`applying` or `clearing`).
|
|
11
|
-
* - A hidden tab stops asking and asks once when it is shown again.
|
|
12
|
-
* - Requests never overlap; the next is timed from the end of the last.
|
|
13
|
-
* - A failed ask is not the end of the job: it backs off and tries again, and
|
|
14
|
-
* reports the error without losing the last good answer.
|
|
15
|
-
*/
|
|
16
|
-
export interface DemoPollEnv {
|
|
17
|
-
isVisible: () => boolean;
|
|
18
|
-
onVisibilityChange: (listener: () => void) => () => void;
|
|
19
|
-
setTimer: (run: () => void, ms: number) => unknown;
|
|
20
|
-
clearTimer: (timer: unknown) => void;
|
|
21
|
-
}
|
|
22
|
-
export declare function browserDemoPollEnv(): DemoPollEnv;
|
|
23
|
-
export interface DemoPollHandlers {
|
|
24
|
-
read: () => Promise<DemoContentStatus>;
|
|
25
|
-
onStatus: (status: DemoContentStatus) => void;
|
|
26
|
-
onError: (error: unknown) => void;
|
|
27
|
-
}
|
|
28
|
-
export interface DemoPoller {
|
|
29
|
-
/** Ask now (after the merchant starts or clears), then keep going while busy. */
|
|
30
|
-
refresh: () => void;
|
|
31
|
-
stop: () => void;
|
|
32
|
-
}
|
|
33
|
-
export declare function startDemoPoller(handlers: DemoPollHandlers, env?: DemoPollEnv): DemoPoller;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { isDemoContentBusy, nextPollDelay } from "./demo-content";
|
|
2
|
-
function browserDemoPollEnv() {
|
|
3
|
-
return {
|
|
4
|
-
isVisible: () => document.visibilityState !== "hidden",
|
|
5
|
-
onVisibilityChange: (listener) => {
|
|
6
|
-
document.addEventListener("visibilitychange", listener);
|
|
7
|
-
return () => document.removeEventListener("visibilitychange", listener);
|
|
8
|
-
},
|
|
9
|
-
setTimer: (run, ms) => window.setTimeout(run, ms),
|
|
10
|
-
clearTimer: (timer) => window.clearTimeout(timer)
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
function startDemoPoller(handlers, env = browserDemoPollEnv()) {
|
|
14
|
-
let timer = null;
|
|
15
|
-
let inFlight = false;
|
|
16
|
-
let stopped = false;
|
|
17
|
-
let attempt = 0;
|
|
18
|
-
let failures = 0;
|
|
19
|
-
let lastKey = "";
|
|
20
|
-
const clear = () => {
|
|
21
|
-
if (timer !== null) env.clearTimer(timer);
|
|
22
|
-
timer = null;
|
|
23
|
-
};
|
|
24
|
-
const schedule = (ms) => {
|
|
25
|
-
clear();
|
|
26
|
-
if (stopped) return;
|
|
27
|
-
timer = env.setTimer(() => {
|
|
28
|
-
timer = null;
|
|
29
|
-
void ask();
|
|
30
|
-
}, ms);
|
|
31
|
-
};
|
|
32
|
-
async function ask() {
|
|
33
|
-
if (stopped || inFlight) return;
|
|
34
|
-
inFlight = true;
|
|
35
|
-
try {
|
|
36
|
-
const status = await handlers.read();
|
|
37
|
-
if (stopped) return;
|
|
38
|
-
failures = 0;
|
|
39
|
-
const key = `${status.state}:${status.progress?.phase}:${status.progress?.done}`;
|
|
40
|
-
attempt = key === lastKey ? attempt + 1 : 0;
|
|
41
|
-
lastKey = key;
|
|
42
|
-
handlers.onStatus(status);
|
|
43
|
-
if (isDemoContentBusy(status)) schedule(nextPollDelay(attempt, 0));
|
|
44
|
-
} catch (error) {
|
|
45
|
-
if (stopped) return;
|
|
46
|
-
failures += 1;
|
|
47
|
-
handlers.onError(error);
|
|
48
|
-
schedule(nextPollDelay(attempt, failures));
|
|
49
|
-
} finally {
|
|
50
|
-
inFlight = false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const stopWatching = env.onVisibilityChange(() => {
|
|
54
|
-
if (stopped) return;
|
|
55
|
-
if (!env.isVisible()) {
|
|
56
|
-
clear();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
clear();
|
|
60
|
-
void ask();
|
|
61
|
-
});
|
|
62
|
-
void ask();
|
|
63
|
-
return {
|
|
64
|
-
refresh: () => {
|
|
65
|
-
clear();
|
|
66
|
-
attempt = 0;
|
|
67
|
-
void ask();
|
|
68
|
-
},
|
|
69
|
-
stop: () => {
|
|
70
|
-
stopped = true;
|
|
71
|
-
clear();
|
|
72
|
-
stopWatching();
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
export {
|
|
77
|
-
browserDemoPollEnv,
|
|
78
|
-
startDemoPoller
|
|
79
|
-
};
|
|
80
|
-
//# sourceMappingURL=demo-content-poller.js.map
|