@curless/shopify-storefront 0.5.0 → 0.7.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/__tests__/assistant.test.d.ts +2 -0
- package/dist/__tests__/assistant.test.d.ts.map +1 -0
- package/dist/__tests__/assistant.test.js +118 -0
- package/dist/__tests__/assistant.test.js.map +1 -0
- package/dist/__tests__/listed-pages.test.d.ts +2 -0
- package/dist/__tests__/listed-pages.test.d.ts.map +1 -0
- package/dist/__tests__/listed-pages.test.js +88 -0
- package/dist/__tests__/listed-pages.test.js.map +1 -0
- package/dist/__tests__/oauth.test.js +50 -1
- package/dist/__tests__/oauth.test.js.map +1 -1
- package/dist/__tests__/tenant.test.js +72 -9
- package/dist/__tests__/tenant.test.js.map +1 -1
- package/dist/assistant.d.ts +31 -0
- package/dist/assistant.d.ts.map +1 -0
- package/dist/assistant.js +91 -0
- package/dist/assistant.js.map +1 -0
- package/dist/index.js +122 -43
- package/dist/index.js.map +1 -1
- package/dist/listed-pages.d.ts +24 -0
- package/dist/listed-pages.d.ts.map +1 -0
- package/dist/listed-pages.js +65 -0
- package/dist/listed-pages.js.map +1 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +26 -3
- package/dist/oauth.js.map +1 -1
- package/dist/tenant.d.ts +11 -0
- package/dist/tenant.d.ts.map +1 -1
- package/dist/tenant.js +43 -2
- package/dist/tenant.js.map +1 -1
- package/dist/tools.d.ts +9 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +1 -1
- package/dist/tools.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/assistant.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// The `attributes[Assistant]` half of order attribution.
|
|
2
|
+
//
|
|
3
|
+
// ⚠️ WHAT SHIPPED: nothing was ever remembered, so every checkout permalink
|
|
4
|
+
// carried `Source=Curless` and no `Assistant`. The registry looked correct —
|
|
5
|
+
// it keyed the client's name on the session id, exactly as it should — but the
|
|
6
|
+
// SERVER never issued a session id, so a spec-following client had none to send
|
|
7
|
+
// back and every lookup missed. Verified in production on 2026-09-10 against a
|
|
8
|
+
// live install: no `Mcp-Session-Id` on the initialize response, one attribute
|
|
9
|
+
// on the live link.
|
|
10
|
+
//
|
|
11
|
+
// So the tests that matter here are about what survives between two DIFFERENT
|
|
12
|
+
// requests, and about the two ways a label silently disappears later: the TTL
|
|
13
|
+
// running from the handshake rather than from use, and an overflow that wipes
|
|
14
|
+
// every live conversation at once.
|
|
15
|
+
import { describe, expect, test } from 'vitest';
|
|
16
|
+
import { AssistantRegistry } from '../assistant.js';
|
|
17
|
+
const HOUR = 60 * 60_000;
|
|
18
|
+
// A clock the tests move on purpose — a registry whose behaviour depends on
|
|
19
|
+
// real time is a registry whose tests depend on how fast the machine is.
|
|
20
|
+
const clock = (start = 1_000_000) => {
|
|
21
|
+
let t = start;
|
|
22
|
+
return {
|
|
23
|
+
now: () => t,
|
|
24
|
+
advance: (ms) => {
|
|
25
|
+
t += ms;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
describe('the name a client gave at initialize', () => {
|
|
30
|
+
test('is found again on a LATER request with the same session', () => {
|
|
31
|
+
const r = new AssistantRegistry();
|
|
32
|
+
r.remember('sess-1', 'claude-ai');
|
|
33
|
+
expect(r.nameFor('sess-1')).toBe('claude-ai');
|
|
34
|
+
});
|
|
35
|
+
test('does not leak to a different session', () => {
|
|
36
|
+
const r = new AssistantRegistry();
|
|
37
|
+
r.remember('sess-1', 'claude-ai');
|
|
38
|
+
expect(r.nameFor('sess-2')).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
test('a request with NO session id gets no name — this was the shipped bug', () => {
|
|
41
|
+
const r = new AssistantRegistry();
|
|
42
|
+
// What actually happened: the server issued no session id, so the client
|
|
43
|
+
// sent none at initialize and none on the tool call.
|
|
44
|
+
r.remember(undefined, 'claude-ai');
|
|
45
|
+
expect(r.size).toBe(0);
|
|
46
|
+
expect(r.nameFor(undefined)).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
test('an anonymous client is remembered as nothing rather than as ""', () => {
|
|
49
|
+
const r = new AssistantRegistry();
|
|
50
|
+
r.remember('sess-1', undefined);
|
|
51
|
+
expect(r.size).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('the User-Agent fallback', () => {
|
|
55
|
+
const r = new AssistantRegistry();
|
|
56
|
+
test('names a host that did not carry the session through', () => {
|
|
57
|
+
expect(r.nameFor('unknown-session', 'ChatGPT-User/1.0')).toBe('ChatGPT-User/1.0');
|
|
58
|
+
});
|
|
59
|
+
test('says nothing for the agents that say nothing about who is shopping', () => {
|
|
60
|
+
for (const ua of [
|
|
61
|
+
'curl/8.7.1',
|
|
62
|
+
'node',
|
|
63
|
+
'undici',
|
|
64
|
+
'python-requests/2.32',
|
|
65
|
+
'Go-http-client/2.0',
|
|
66
|
+
]) {
|
|
67
|
+
expect(r.nameFor('unknown-session', ua)).toBeUndefined();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
test('a remembered session BEATS the User-Agent', () => {
|
|
71
|
+
const reg = new AssistantRegistry();
|
|
72
|
+
reg.remember('sess-1', 'claude-ai');
|
|
73
|
+
expect(reg.nameFor('sess-1', 'ChatGPT-User/1.0')).toBe('claude-ai');
|
|
74
|
+
});
|
|
75
|
+
test('is truncated — it lands in a URL a buyer sees', () => {
|
|
76
|
+
expect(r.nameFor('x', 'A'.repeat(200))).toHaveLength(60);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('a label that disappears later is worse than one that never appeared', () => {
|
|
80
|
+
test('an ACTIVE session keeps its name past the TTL', () => {
|
|
81
|
+
// The merchant would otherwise see `Assistant` on a conversation's early
|
|
82
|
+
// orders and nothing on its later ones, which reads as the assistant having
|
|
83
|
+
// changed. Reading has to touch the entry.
|
|
84
|
+
const c = clock();
|
|
85
|
+
const r = new AssistantRegistry({ ttlMs: HOUR, now: c.now });
|
|
86
|
+
r.remember('sess-1', 'claude-ai');
|
|
87
|
+
for (let i = 0; i < 5; i += 1) {
|
|
88
|
+
c.advance(HOUR - 60_000);
|
|
89
|
+
expect(r.nameFor('sess-1')).toBe('claude-ai');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
test('an IDLE session is dropped once the TTL passes', () => {
|
|
93
|
+
const c = clock();
|
|
94
|
+
const r = new AssistantRegistry({ ttlMs: HOUR, now: c.now });
|
|
95
|
+
r.remember('sess-1', 'claude-ai');
|
|
96
|
+
c.advance(HOUR + 1);
|
|
97
|
+
expect(r.nameFor('sess-1')).toBeUndefined();
|
|
98
|
+
expect(r.size).toBe(0);
|
|
99
|
+
});
|
|
100
|
+
test('overflow evicts the OLDEST, not every live conversation', () => {
|
|
101
|
+
const c = clock();
|
|
102
|
+
const r = new AssistantRegistry({ max: 3, now: c.now });
|
|
103
|
+
for (const s of ['a', 'b', 'c']) {
|
|
104
|
+
r.remember(s, `client-${s}`);
|
|
105
|
+
c.advance(1_000);
|
|
106
|
+
}
|
|
107
|
+
// `a` is the least recently used; touching it makes `b` the victim instead.
|
|
108
|
+
expect(r.nameFor('a')).toBe('client-a');
|
|
109
|
+
c.advance(1_000);
|
|
110
|
+
r.remember('d', 'client-d');
|
|
111
|
+
expect(r.size).toBe(3);
|
|
112
|
+
expect(r.nameFor('b')).toBeUndefined();
|
|
113
|
+
expect(r.nameFor('a')).toBe('client-a');
|
|
114
|
+
expect(r.nameFor('c')).toBe('client-c');
|
|
115
|
+
expect(r.nameFor('d')).toBe('client-d');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=assistant.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.test.js","sourceRoot":"","sources":["../../src/__tests__/assistant.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listed-pages.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/listed-pages.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// The listed app's merchant-facing pages.
|
|
2
|
+
//
|
|
3
|
+
// These went out wrong once, in the way that is hardest to notice from the
|
|
4
|
+
// code: the server ran the SINGLE-shop status page for an app that has no shop,
|
|
5
|
+
// so the address Shopify opens after an install said "This store is not
|
|
6
|
+
// connected yet · Connect " — an empty shop name, behind a link that answered
|
|
7
|
+
// 400. What is pinned here is therefore what the pages SAY, not that they
|
|
8
|
+
// render: that the connected page names the MERCHANT's store rather than our
|
|
9
|
+
// product, and that it carries the connector address, which is the only thing
|
|
10
|
+
// on it a merchant can act on.
|
|
11
|
+
import { describe, expect, test } from 'vitest';
|
|
12
|
+
import { connectPage, connectedPage, connectorUrl, listedLandingPage, } from '../listed-pages.js';
|
|
13
|
+
const ctx = {
|
|
14
|
+
appUrl: 'https://storefront.curless.ai',
|
|
15
|
+
productName: 'Curless Storefront',
|
|
16
|
+
};
|
|
17
|
+
const snap = (over = {}) => ({
|
|
18
|
+
products: [{ handle: 'a' }, { handle: 'b' }],
|
|
19
|
+
loadedAt: new Date('2026-09-10T12:34:56Z'),
|
|
20
|
+
discounts: [],
|
|
21
|
+
...over,
|
|
22
|
+
});
|
|
23
|
+
describe('connectorUrl', () => {
|
|
24
|
+
test('is the shop-scoped MCP path under the app URL', () => {
|
|
25
|
+
expect(connectorUrl(ctx, 'curless-demo.myshopify.com')).toBe('https://storefront.curless.ai/s/curless-demo.myshopify.com/mcp');
|
|
26
|
+
});
|
|
27
|
+
test('a trailing slash on the app URL does not double up', () => {
|
|
28
|
+
expect(connectorUrl({ ...ctx, appUrl: 'https://storefront.curless.ai/' }, 'x.myshopify.com')).toBe('https://storefront.curless.ai/s/x.myshopify.com/mcp');
|
|
29
|
+
});
|
|
30
|
+
test('lowercases the shop — the path resolver keys on it', () => {
|
|
31
|
+
expect(connectorUrl(ctx, 'Curless-Demo.MyShopify.com')).toContain('/s/curless-demo.myshopify.com/mcp');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('connectedPage', () => {
|
|
35
|
+
const html = connectedPage(ctx, 'curless-demo.myshopify.com', snap());
|
|
36
|
+
test('names the MERCHANT store, not our product', () => {
|
|
37
|
+
expect(html).toContain('curless-demo.myshopify.com');
|
|
38
|
+
// The bug this replaces: the callback congratulated the merchant on
|
|
39
|
+
// "Curless Storefront is connected" — our own name, on their install.
|
|
40
|
+
expect(html).not.toContain('Curless Storefront is connected');
|
|
41
|
+
});
|
|
42
|
+
test('carries the connector address — the one thing the merchant must copy', () => {
|
|
43
|
+
expect(html).toContain('https://storefront.curless.ai/s/curless-demo.myshopify.com/mcp');
|
|
44
|
+
});
|
|
45
|
+
test('reports the catalogue that was actually read', () => {
|
|
46
|
+
expect(html).toContain('2 products readable');
|
|
47
|
+
expect(html).toContain('2026-09-10 12:34');
|
|
48
|
+
});
|
|
49
|
+
test('a store whose last sync failed is told so, and still gets the address', () => {
|
|
50
|
+
const failed = connectedPage(ctx, 'x.myshopify.com', snap({ lastError: 'HTTP 401' }));
|
|
51
|
+
expect(failed).toContain('HTTP 401');
|
|
52
|
+
expect(failed).toContain('/s/x.myshopify.com/mcp');
|
|
53
|
+
});
|
|
54
|
+
test('a catalogue that has never loaded says nothing about when it synced', () => {
|
|
55
|
+
expect(connectedPage(ctx, 'x.myshopify.com', snap({ products: [], loadedAt: null }))).not.toContain('last synced');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('connectPage', () => {
|
|
59
|
+
test('offers the install for THAT shop', () => {
|
|
60
|
+
expect(connectPage('curless-demo.myshopify.com')).toContain('href="/auth/install?shop=curless-demo.myshopify.com"');
|
|
61
|
+
});
|
|
62
|
+
test('is not the empty-shop page that shipped', () => {
|
|
63
|
+
// The regression, exactly: a button reading "Connect " with nothing after it.
|
|
64
|
+
expect(connectPage('curless-demo.myshopify.com')).not.toContain('Connect </a>');
|
|
65
|
+
});
|
|
66
|
+
test('a shop with markup in it cannot break out of the link or the text', () => {
|
|
67
|
+
const html = connectPage('evil".myshopify.com');
|
|
68
|
+
expect(html).not.toContain('evil".myshopify.com');
|
|
69
|
+
expect(html).toContain('evil".myshopify.com');
|
|
70
|
+
expect(html).toContain('shop=evil%22.myshopify.com');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('listedLandingPage', () => {
|
|
74
|
+
const html = listedLandingPage(ctx);
|
|
75
|
+
test('says what the app is without naming any merchant', () => {
|
|
76
|
+
expect(html).toContain('Curless Storefront');
|
|
77
|
+
expect(html).not.toContain('myshopify.com');
|
|
78
|
+
});
|
|
79
|
+
test('links the four pages a reviewer looks for', () => {
|
|
80
|
+
for (const href of ['/docs', '/privacy', '/terms', '/security']) {
|
|
81
|
+
expect(html).toContain(`href="${href}"`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
test('says it is read-only, which is the listing claim', () => {
|
|
85
|
+
expect(html).toContain('read-only');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=listed-pages.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listed-pages.test.js","sourceRoot":"","sources":["../../src/__tests__/listed-pages.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// which is exactly why they need tests rather than a careful reading.
|
|
4
4
|
import { createHmac } from 'node:crypto';
|
|
5
5
|
import { describe, expect, test, vi } from 'vitest';
|
|
6
|
-
import { exchangeCode, installUrl, isShopDomain, makeState, verifyCallbackHmac, verifyState, } from '../oauth.js';
|
|
6
|
+
import { exchangeCode, installUrl, isShopDomain, makeState, refreshAccessToken, verifyCallbackHmac, verifyState, } from '../oauth.js';
|
|
7
7
|
const CONFIG = {
|
|
8
8
|
clientId: 'cid',
|
|
9
9
|
clientSecret: 'shhh',
|
|
@@ -108,5 +108,54 @@ describe('code exchange', () => {
|
|
|
108
108
|
await expect(exchangeCode(CONFIG, 'bad', f)).rejects.toThrow(/HTTP 400/);
|
|
109
109
|
await expect(exchangeCode(CONFIG, 'bad', f)).rejects.not.toThrow(/reflected/);
|
|
110
110
|
});
|
|
111
|
+
test('posts to the SHOP in the config, not to some default', async () => {
|
|
112
|
+
const f = vi.fn(async () => new Response(JSON.stringify({ access_token: 'shpat_x', scope: 'read_products' }), {
|
|
113
|
+
status: 200,
|
|
114
|
+
headers: { 'content-type': 'application/json' },
|
|
115
|
+
}));
|
|
116
|
+
await exchangeCode({ ...CONFIG, shopDomain: 'other.myshopify.com' }, 'code', f);
|
|
117
|
+
expect(vi.mocked(f).mock.calls[0]?.[0]).toBe('https://other.myshopify.com/admin/oauth/access_token');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
// ⚠️ THE LISTED APP HAS NO SHOP OF ITS OWN — its module-level config holds `''`,
|
|
121
|
+
// because the shop is a property of the REQUEST, not of the config. Every call
|
|
122
|
+
// site therefore has to spread the real shop in, and on 2026-09-10 the callback
|
|
123
|
+
// did not: it built `https:///admin/oauth/access_token`, undici answered
|
|
124
|
+
// `fetch failed`, and a real install died looking exactly like a network fault.
|
|
125
|
+
// An hour went into DNS and container egress before anyone looked at the host.
|
|
126
|
+
//
|
|
127
|
+
// These are the tests that would have caught it. Note that NONE of the 27 that
|
|
128
|
+
// existed did: every one of them passed a config with a shop already in it.
|
|
129
|
+
describe('an empty shop is refused out loud, not fetched', () => {
|
|
130
|
+
const NO_SHOP = { ...CONFIG, shopDomain: '' };
|
|
131
|
+
test('exchangeCode throws before it fetches anything', async () => {
|
|
132
|
+
const f = vi.fn();
|
|
133
|
+
await expect(exchangeCode(NO_SHOP, 'code', f)).rejects.toThrow(/no shop to talk to/);
|
|
134
|
+
// The point of failing early: nothing was sent to `https:///…`.
|
|
135
|
+
expect(vi.mocked(f)).not.toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
test('refreshAccessToken throws before it fetches anything', async () => {
|
|
138
|
+
const f = vi.fn();
|
|
139
|
+
await expect(refreshAccessToken(NO_SHOP, 'rt', f)).rejects.toThrow(/no shop to talk to/);
|
|
140
|
+
expect(vi.mocked(f)).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
test('installUrl throws rather than returning https:///admin/oauth/authorize', () => {
|
|
143
|
+
expect(() => installUrl(NO_SHOP, 'st.at')).toThrow(/no shop to talk to/);
|
|
144
|
+
});
|
|
145
|
+
test('the error names the empty value, so the log says what is missing', async () => {
|
|
146
|
+
await expect(exchangeCode(NO_SHOP, 'code')).rejects.toThrow(/shopDomain is ""/);
|
|
147
|
+
});
|
|
148
|
+
test('a lookalike host is refused by the same door', async () => {
|
|
149
|
+
const evil = { ...CONFIG, shopDomain: 'shop.myshopify.com.evil.com' };
|
|
150
|
+
await expect(exchangeCode(evil, 'code')).rejects.toThrow(/no shop to talk to/);
|
|
151
|
+
});
|
|
152
|
+
test('spreading the request shop over the listed config is what works', async () => {
|
|
153
|
+
const f = vi.fn(async () => new Response(JSON.stringify({ access_token: 'shpat_x', scope: 'read_products' }), {
|
|
154
|
+
status: 200,
|
|
155
|
+
headers: { 'content-type': 'application/json' },
|
|
156
|
+
}));
|
|
157
|
+
await exchangeCode({ ...NO_SHOP, shopDomain: 'curless-demo.myshopify.com' }, 'code', f);
|
|
158
|
+
expect(vi.mocked(f).mock.calls[0]?.[0]).toBe('https://curless-demo.myshopify.com/admin/oauth/access_token');
|
|
159
|
+
});
|
|
111
160
|
});
|
|
112
161
|
//# sourceMappingURL=oauth.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.test.js","sourceRoot":"","sources":["../../src/__tests__/oauth.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oauth.test.js","sourceRoot":"","sources":["../../src/__tests__/oauth.test.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,iFAAiF;AACjF,sEAAsE;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,EAEL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,GAAgB;IAC1B,QAAQ,EAAE,KAAK;IACf,YAAY,EAAE,MAAM;IACpB,UAAU,EAAE,2BAA2B;IACvC,MAAM,EAAE,8BAA8B;IACtC,MAAM,EAAE,2BAA2B;CACpC,CAAC;AAEF,0EAA0E;AAC1E,gDAAgD;AAChD,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,KAA6B,EAAU,EAAE;IAC5E,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;SAC7B,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,EAAE,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChF,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;AAC5C,CAAC,CAAC;AAEF,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7D,sEAAsE;QACtE,MAAM,CAAC,YAAY,CAAC,oCAAoC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,YAAY,CAAC,oCAAoC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;IAEnF,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,kEAAkE;IAClE,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,gCAAgC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,0EAA0E;IAC1E,6EAA6E;IAC7E,+EAA+E;IAC/E,iBAAiB;IACjB,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC7D,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrE,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,0EAA0E;IAC1E,6EAA6E;IAC7E,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAChC,MAAM,GAAG,GAAG,iBAAiB,CAAC;QAC9B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACrC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAClD,MAAM,GAAG,GAAG,iBAAiB,CAAC;QAC9B,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;QAC1E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QACxE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QAChG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpD,6EAA6E;QAC7E,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CACb,KAAK,IAAI,EAAE,CACT,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,EAAE;YAChF,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CACsB,CAAC;QAC7B,qEAAqE;QACrE,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YAC7D,WAAW,EAAE,SAAS;YACtB,KAAK,EAAE,eAAe;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CACb,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,kCAAkC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CACnD,CAAC;QAC7B,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzE,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CACb,KAAK,IAAI,EAAE,CACT,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,EAAE;YAChF,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CACsB,CAAC;QAC7B,MAAM,YAAY,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,qBAAqB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC1C,sDAAsD,CACvD,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AACjF,+EAA+E;AAC/E,gFAAgF;AAChF,yEAAyE;AACzE,gFAAgF;AAChF,+EAA+E;AAC/E,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,QAAQ,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC9D,MAAM,OAAO,GAAgB,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAE3D,IAAI,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,EAA6B,CAAC;QAC7C,MAAM,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACrF,gEAAgE;QAChE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,EAA6B,CAAC;QAC7C,MAAM,MAAM,CAAC,kBAAkB,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACzF,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAClF,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAClF,MAAM,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,6BAA6B,EAAE,CAAC;QACtE,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CACb,KAAK,IAAI,EAAE,CACT,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,EAAE;YAChF,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CACsB,CAAC;QAC7B,MAAM,YAAY,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,4BAA4B,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACxF,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC1C,6DAA6D,CAC9D,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
import { resolveStorefront } from '../config.js';
|
|
3
3
|
import { explainNoTenant, multiShopResolver, singleShopResolver } from '../tenant.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const fakeCatalog = (tag, opts = {}) => {
|
|
5
|
+
const snap = { products: [], loadedAt: opts.loaded ? new Date() : null };
|
|
6
|
+
return {
|
|
7
|
+
tag,
|
|
8
|
+
get current() {
|
|
9
|
+
return snap;
|
|
10
|
+
},
|
|
11
|
+
refresh: async () => {
|
|
12
|
+
opts.refreshes?.push(tag);
|
|
13
|
+
if (opts.loadTakes)
|
|
14
|
+
await new Promise((r) => setTimeout(r, opts.loadTakes));
|
|
15
|
+
snap.products = [{ handle: 'loaded-by-the-wait' }];
|
|
16
|
+
snap.loadedAt = new Date();
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
8
20
|
const token = (shopDomain = 'rqxbft-fw.myshopify.com') => ({
|
|
9
21
|
shopDomain,
|
|
10
22
|
accessToken: 'x',
|
|
@@ -19,22 +31,23 @@ const single = (opts) => singleShopResolver({
|
|
|
19
31
|
urls: { appUrl: 'https://shopify.curless.ai/moody' },
|
|
20
32
|
legal: {},
|
|
21
33
|
}),
|
|
22
|
-
catalog: fakeCatalog('single'),
|
|
34
|
+
catalog: fakeCatalog('single', { loaded: true }),
|
|
23
35
|
tokens: { get: async () => (opts.installed ? token() : null) },
|
|
24
36
|
});
|
|
25
|
-
const multi = (installed) => {
|
|
37
|
+
const multi = (installed, opts = {}) => {
|
|
26
38
|
const started = [];
|
|
39
|
+
const refreshes = [];
|
|
27
40
|
const registry = {
|
|
28
41
|
for: (s) => {
|
|
29
42
|
started.push(s);
|
|
30
|
-
return fakeCatalog(s);
|
|
43
|
+
return fakeCatalog(s, { loaded: !opts.cold, loadTakes: opts.loadTakes, refreshes });
|
|
31
44
|
},
|
|
32
45
|
};
|
|
33
46
|
const tokens = {
|
|
34
47
|
get: async (s) => (installed.includes(s) ? token(s) : null),
|
|
35
48
|
};
|
|
36
|
-
const resolve = multiShopResolver({ registry, tokens });
|
|
37
|
-
return { resolve, started };
|
|
49
|
+
const resolve = multiShopResolver({ registry, tokens, coldStartWaitMs: 50 });
|
|
50
|
+
return { resolve, started, refreshes };
|
|
38
51
|
};
|
|
39
52
|
describe('branded deployment', () => {
|
|
40
53
|
test('a request naming nothing gets the one shop it serves', async () => {
|
|
@@ -102,4 +115,54 @@ describe('what the buyer is told', () => {
|
|
|
102
115
|
expect(explainNoTenant({ reason: 'not-installed', shop: 'a.myshopify.com' })).toMatch(/has not installed/i);
|
|
103
116
|
});
|
|
104
117
|
});
|
|
118
|
+
// ⚠️ THE FIRST QUESTION EVER ASKED OF A SHOP.
|
|
119
|
+
//
|
|
120
|
+
// `registry.for` creates the catalogue and starts its first load; the resolver
|
|
121
|
+
// used to hand that empty catalogue straight to the tool, which answered "the
|
|
122
|
+
// catalog is not loaded yet". Verified in production on 2026-09-10, one minute
|
|
123
|
+
// after a real install on the dev store: ask once, get the error card; ask
|
|
124
|
+
// again, get 15 products. A Shopify reviewer's step 4 is exactly that first ask.
|
|
125
|
+
describe('a catalogue that has never loaded', () => {
|
|
126
|
+
test('is waited for, so the first request answers with products', async () => {
|
|
127
|
+
const { resolve } = multi(['rqxbft-fw.myshopify.com'], { cold: true });
|
|
128
|
+
const r = await resolve('rqxbft-fw.myshopify.com');
|
|
129
|
+
expect(r.ok).toBe(true);
|
|
130
|
+
expect(r.ok && r.tenant.catalog.current.loadedAt).not.toBeNull();
|
|
131
|
+
expect(r.ok && r.tenant.catalog.current.products).toHaveLength(1);
|
|
132
|
+
});
|
|
133
|
+
test('a WARM catalogue is not refreshed on the way past', async () => {
|
|
134
|
+
const { resolve, refreshes } = multi(['rqxbft-fw.myshopify.com']);
|
|
135
|
+
await resolve('rqxbft-fw.myshopify.com');
|
|
136
|
+
expect(refreshes).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
test('the wait is BOUNDED — a load that hangs still answers', async () => {
|
|
139
|
+
// Nothing between the catalogue and Shopify sets a timeout, so an unbounded
|
|
140
|
+
// await here would hand a buyer's request to whatever Shopify does with it.
|
|
141
|
+
const { resolve } = multi(['rqxbft-fw.myshopify.com'], { cold: true, loadTakes: 5_000 });
|
|
142
|
+
const started = Date.now();
|
|
143
|
+
const r = await resolve('rqxbft-fw.myshopify.com');
|
|
144
|
+
expect(Date.now() - started).toBeLessThan(1_000);
|
|
145
|
+
// Answered anyway, with the empty snapshot — the old behaviour, but only
|
|
146
|
+
// after actually waiting for the load rather than instead of waiting.
|
|
147
|
+
expect(r.ok && r.tenant.catalog.current.loadedAt).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
test('the branded resolver gets the same wait', async () => {
|
|
150
|
+
const refreshes = [];
|
|
151
|
+
const resolve = singleShopResolver({
|
|
152
|
+
store: resolveStorefront({
|
|
153
|
+
id: 'moody',
|
|
154
|
+
server: { name: 'moody', title: 'Moody' },
|
|
155
|
+
shopify: { mode: 'env', shopDomain: 'rqxbft-fw.myshopify.com' },
|
|
156
|
+
urls: { appUrl: 'https://shopify.curless.ai/moody' },
|
|
157
|
+
legal: {},
|
|
158
|
+
}),
|
|
159
|
+
catalog: fakeCatalog('cold', { refreshes }),
|
|
160
|
+
tokens: { get: async () => token() },
|
|
161
|
+
coldStartWaitMs: 50,
|
|
162
|
+
});
|
|
163
|
+
const r = await resolve();
|
|
164
|
+
expect(refreshes).toEqual(['cold']);
|
|
165
|
+
expect(r.ok && r.tenant.catalog.current.loadedAt).not.toBeNull();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
105
168
|
//# sourceMappingURL=tenant.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.test.js","sourceRoot":"","sources":["../../src/__tests__/tenant.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"tenant.test.js","sourceRoot":"","sources":["../../src/__tests__/tenant.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type AssistantRegistryOptions = {
|
|
2
|
+
/** Drop a session this long after its last USE, not after its first sight. */
|
|
3
|
+
ttlMs?: number;
|
|
4
|
+
/** Hard ceiling on remembered sessions; the least recently used goes first. */
|
|
5
|
+
max?: number;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
};
|
|
8
|
+
export declare class AssistantRegistry {
|
|
9
|
+
private readonly entries;
|
|
10
|
+
private readonly ttlMs;
|
|
11
|
+
private readonly max;
|
|
12
|
+
private readonly now;
|
|
13
|
+
constructor(opts?: AssistantRegistryOptions);
|
|
14
|
+
/** Remembered sessions — for a status page and for the tests. */
|
|
15
|
+
get size(): number;
|
|
16
|
+
/** Record what a client called itself at `initialize`. */
|
|
17
|
+
remember(sessionId: string | undefined, name: string | undefined): void;
|
|
18
|
+
/**
|
|
19
|
+
* The label for THIS request.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ READING TOUCHES THE ENTRY. Without that the TTL runs from `initialize`,
|
|
22
|
+
* so a conversation that goes on longer than an hour silently loses its name
|
|
23
|
+
* mid-session — the merchant then sees `Assistant` on the early orders of a
|
|
24
|
+
* session and nothing on the later ones, which is worse than never having it,
|
|
25
|
+
* because it looks like the assistant changed.
|
|
26
|
+
*/
|
|
27
|
+
nameFor(sessionId: string | undefined, userAgent?: string): string | undefined;
|
|
28
|
+
private sweep;
|
|
29
|
+
private evictOverflow;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=assistant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../src/assistant.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,wBAAwB,GAAG;IACrC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,GAAE,wBAA6B;IAM/C,iEAAiE;IACjE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAOvE;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAc9E,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,aAAa;CActB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Who is shopping — the label that ends up on the merchant's order.
|
|
2
|
+
//
|
|
3
|
+
// ⚠️ THIS IS THE HALF OF ATTRIBUTION THAT TELLS A MERCHANT *WHICH* ASSISTANT.
|
|
4
|
+
// The checkout permalink carries `attributes[Source]=Curless`, which is what
|
|
5
|
+
// makes an order recognisably ours; `attributes[Assistant]` is what makes it
|
|
6
|
+
// recognisably ChatGPT's or Claude's. A merchant deciding whether this channel
|
|
7
|
+
// is worth anything wants the second one too.
|
|
8
|
+
//
|
|
9
|
+
// The awkward part is that an MCP client says who it is exactly ONCE, in
|
|
10
|
+
// `initialize`, and the tool call that builds the permalink is a DIFFERENT
|
|
11
|
+
// request. So the name has to be remembered against something both requests
|
|
12
|
+
// carry — which is the session id, and which is why this file exists at all.
|
|
13
|
+
//
|
|
14
|
+
// ⚠️ AND THE SERVER HAS TO ISSUE THAT SESSION ID. It did not, so a
|
|
15
|
+
// spec-following client had none to send back, nothing was ever remembered, and
|
|
16
|
+
// every permalink went out with `Source` and no `Assistant`. Verified in
|
|
17
|
+
// production on 2026-09-10: the initialize response carried no `Mcp-Session-Id`
|
|
18
|
+
// header and the live checkout link had one attribute, not two.
|
|
19
|
+
/** Clients whose User-Agent says nothing about who is shopping. */
|
|
20
|
+
const ANONYMOUS_UA = /^(node|curl|undici|python|go-http|okhttp|axios|got|wget)/i;
|
|
21
|
+
export class AssistantRegistry {
|
|
22
|
+
entries = new Map();
|
|
23
|
+
ttlMs;
|
|
24
|
+
max;
|
|
25
|
+
now;
|
|
26
|
+
constructor(opts = {}) {
|
|
27
|
+
this.ttlMs = opts.ttlMs ?? 60 * 60_000;
|
|
28
|
+
this.max = opts.max ?? 5_000;
|
|
29
|
+
this.now = opts.now ?? (() => Date.now());
|
|
30
|
+
}
|
|
31
|
+
/** Remembered sessions — for a status page and for the tests. */
|
|
32
|
+
get size() {
|
|
33
|
+
return this.entries.size;
|
|
34
|
+
}
|
|
35
|
+
/** Record what a client called itself at `initialize`. */
|
|
36
|
+
remember(sessionId, name) {
|
|
37
|
+
if (!sessionId || !name)
|
|
38
|
+
return;
|
|
39
|
+
this.sweep();
|
|
40
|
+
this.entries.set(sessionId, { name, lastUsed: this.now() });
|
|
41
|
+
this.evictOverflow();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The label for THIS request.
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ READING TOUCHES THE ENTRY. Without that the TTL runs from `initialize`,
|
|
47
|
+
* so a conversation that goes on longer than an hour silently loses its name
|
|
48
|
+
* mid-session — the merchant then sees `Assistant` on the early orders of a
|
|
49
|
+
* session and nothing on the later ones, which is worse than never having it,
|
|
50
|
+
* because it looks like the assistant changed.
|
|
51
|
+
*/
|
|
52
|
+
nameFor(sessionId, userAgent) {
|
|
53
|
+
this.sweep();
|
|
54
|
+
const hit = sessionId ? this.entries.get(sessionId) : undefined;
|
|
55
|
+
if (hit) {
|
|
56
|
+
hit.lastUsed = this.now();
|
|
57
|
+
return hit.name;
|
|
58
|
+
}
|
|
59
|
+
// A host that ignores the session id can still be recognisable by its
|
|
60
|
+
// User-Agent. It is a label on an order, not an authorisation decision, so
|
|
61
|
+
// a best guess is fine and a wrong one is harmless.
|
|
62
|
+
if (!userAgent || ANONYMOUS_UA.test(userAgent))
|
|
63
|
+
return undefined;
|
|
64
|
+
return userAgent.slice(0, 60);
|
|
65
|
+
}
|
|
66
|
+
sweep() {
|
|
67
|
+
const cutoff = this.now() - this.ttlMs;
|
|
68
|
+
for (const [k, e] of this.entries)
|
|
69
|
+
if (e.lastUsed < cutoff)
|
|
70
|
+
this.entries.delete(k);
|
|
71
|
+
}
|
|
72
|
+
// ⚠️ Evict the OLDEST, not everything. The previous version cleared the whole
|
|
73
|
+
// map on overflow, which drops every live conversation's label at once — one
|
|
74
|
+
// busy minute and every merchant's orders lose their `Assistant` together.
|
|
75
|
+
evictOverflow() {
|
|
76
|
+
while (this.entries.size > this.max) {
|
|
77
|
+
let oldestKey = null;
|
|
78
|
+
let oldest = Number.POSITIVE_INFINITY;
|
|
79
|
+
for (const [k, e] of this.entries) {
|
|
80
|
+
if (e.lastUsed < oldest) {
|
|
81
|
+
oldest = e.lastUsed;
|
|
82
|
+
oldestKey = k;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (oldestKey === null)
|
|
86
|
+
return;
|
|
87
|
+
this.entries.delete(oldestKey);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=assistant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.js","sourceRoot":"","sources":["../src/assistant.ts"],"names":[],"mappings":"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"}
|