@chatlychat/react-native 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/client/RealtimeClient.d.ts.map +1 -1
- package/dist/client/RealtimeClient.js +3 -1
- package/dist/client/RealtimeClient.js.map +1 -1
- package/dist/client/RestClient.d.ts.map +1 -1
- package/dist/client/RestClient.js +16 -1
- package/dist/client/RestClient.js.map +1 -1
- package/dist/client/types.d.ts +1 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/useChatly.d.ts.map +1 -1
- package/dist/useChatly.js.map +1 -1
- package/package.json +22 -13
- package/dist/blocks/money.parity.spec.d.ts +0 -2
- package/dist/blocks/money.parity.spec.d.ts.map +0 -1
- package/dist/blocks/money.parity.spec.js +0 -87
- package/dist/blocks/money.parity.spec.js.map +0 -1
- package/dist/blocks/read-envelope.parity.spec.d.ts +0 -2
- package/dist/blocks/read-envelope.parity.spec.d.ts.map +0 -1
- package/dist/blocks/read-envelope.parity.spec.js +0 -146
- package/dist/blocks/read-envelope.parity.spec.js.map +0 -1
- package/dist/client/RealtimeClient.spec.d.ts +0 -2
- package/dist/client/RealtimeClient.spec.d.ts.map +0 -1
- package/dist/client/RealtimeClient.spec.js +0 -123
- package/dist/client/RealtimeClient.spec.js.map +0 -1
- package/dist/client/RestClient.spec.d.ts +0 -2
- package/dist/client/RestClient.spec.d.ts.map +0 -1
- package/dist/client/RestClient.spec.js +0 -267
- package/dist/client/RestClient.spec.js.map +0 -1
- package/dist/components/MessageBubble.spec.d.ts +0 -2
- package/dist/components/MessageBubble.spec.d.ts.map +0 -1
- package/dist/components/MessageBubble.spec.js +0 -207
- package/dist/components/MessageBubble.spec.js.map +0 -1
- package/dist/components/MessageCanvas.spec.d.ts +0 -2
- package/dist/components/MessageCanvas.spec.d.ts.map +0 -1
- package/dist/components/MessageCanvas.spec.js +0 -142
- package/dist/components/MessageCanvas.spec.js.map +0 -1
- package/dist/test/react-native-dom.d.ts +0 -97
- package/dist/test/react-native-dom.d.ts.map +0 -1
- package/dist/test/react-native-dom.js +0 -155
- package/dist/test/react-native-dom.js.map +0 -1
- package/dist/test/react-native-dom.spec.d.ts +0 -2
- package/dist/test/react-native-dom.spec.d.ts.map +0 -1
- package/dist/test/react-native-dom.spec.js +0 -83
- package/dist/test/react-native-dom.spec.js.map +0 -1
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The React Native SDK's realtime wire protocol.
|
|
3
|
-
*
|
|
4
|
-
* Every channel name here was wrong, so the whole surface was inert — for
|
|
5
|
-
* every integrator, silently:
|
|
6
|
-
*
|
|
7
|
-
* - it LISTENED on 'envelope'; the gateway EMITS on 'event', so nothing
|
|
8
|
-
* ever arrived;
|
|
9
|
-
* - it EMITTED subscribe on 'envelope'; the gateway LISTENS on 'subscribe',
|
|
10
|
-
* so no subscription was ever registered;
|
|
11
|
-
* - it wrapped the subscribe payload in a RealtimeEnvelope; the gateway
|
|
12
|
-
* parses the BARE `{ channels, lastEventId? }` with `subscribePayloadSchema`,
|
|
13
|
-
* so even on the right channel it would have answered validation_failed.
|
|
14
|
-
*
|
|
15
|
-
* Nothing failed loudly. The socket connected, the SDK reported itself
|
|
16
|
-
* healthy, and no message ever appeared.
|
|
17
|
-
*
|
|
18
|
-
* These pin the protocol against the server's own definitions.
|
|
19
|
-
*/
|
|
20
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
21
|
-
const emitted = [];
|
|
22
|
-
const handlers = new Map();
|
|
23
|
-
vi.mock('socket.io-client', () => ({
|
|
24
|
-
io: () => ({
|
|
25
|
-
// `subscribe()` gates on `socket.connected`, exactly as socket.io-client
|
|
26
|
-
// exposes it — a mock without this silently emits nothing.
|
|
27
|
-
connected: true,
|
|
28
|
-
on: (channel, fn) => {
|
|
29
|
-
handlers.set(channel, fn);
|
|
30
|
-
},
|
|
31
|
-
emit: (channel, payload) => {
|
|
32
|
-
emitted.push({ channel, payload });
|
|
33
|
-
},
|
|
34
|
-
disconnect: vi.fn(),
|
|
35
|
-
close: vi.fn(),
|
|
36
|
-
}),
|
|
37
|
-
}));
|
|
38
|
-
const { RealtimeClient } = await import('./RealtimeClient.js');
|
|
39
|
-
const SESSION = { workspaceId: 'ws-1', contactId: 'c-1' };
|
|
40
|
-
function connected() {
|
|
41
|
-
emitted.length = 0;
|
|
42
|
-
handlers.clear();
|
|
43
|
-
const c = new RealtimeClient({ apiHost: 'api.test' });
|
|
44
|
-
c.connect(SESSION, 'tok');
|
|
45
|
-
return c;
|
|
46
|
-
}
|
|
47
|
-
describe('inbound channel', () => {
|
|
48
|
-
it('listens on the channel the gateway emits — "event"', () => {
|
|
49
|
-
connected();
|
|
50
|
-
expect(handlers.has('event')).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
it('no longer listens on "envelope", which the server never emits', () => {
|
|
53
|
-
connected();
|
|
54
|
-
expect(handlers.has('envelope')).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
it('delivers a received envelope to a subscriber', () => {
|
|
57
|
-
const c = connected();
|
|
58
|
-
const seen = [];
|
|
59
|
-
c.on('message.created', (e) => void seen.push(e));
|
|
60
|
-
handlers.get('event')?.({
|
|
61
|
-
v: 1,
|
|
62
|
-
type: 'message.created',
|
|
63
|
-
id: '01000000-0000-7000-8000-000000000001',
|
|
64
|
-
ts: Date.now(),
|
|
65
|
-
workspace_id: 'ws-1',
|
|
66
|
-
payload: { body: 'hi' },
|
|
67
|
-
});
|
|
68
|
-
expect(seen).toHaveLength(1);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
describe('subscribe frame', () => {
|
|
72
|
-
it('emits on "subscribe", the channel the gateway listens on', () => {
|
|
73
|
-
const c = connected();
|
|
74
|
-
c.subscribe(['conversation:1']);
|
|
75
|
-
expect(emitted.map((e) => e.channel)).toContain('subscribe');
|
|
76
|
-
});
|
|
77
|
-
it('never emits on "envelope"', () => {
|
|
78
|
-
const c = connected();
|
|
79
|
-
c.subscribe(['conversation:1']);
|
|
80
|
-
expect(emitted.map((e) => e.channel)).not.toContain('envelope');
|
|
81
|
-
});
|
|
82
|
-
it('sends the BARE payload the server parses, not an envelope', () => {
|
|
83
|
-
const c = connected();
|
|
84
|
-
c.subscribe(['conversation:1']);
|
|
85
|
-
const frame = emitted.find((e) => e.channel === 'subscribe')?.payload;
|
|
86
|
-
expect(frame['channels']).toEqual(['conversation:1']);
|
|
87
|
-
// An envelope would carry these; the schema rejects unknown top-level keys.
|
|
88
|
-
expect(frame['v']).toBeUndefined();
|
|
89
|
-
expect(frame['type']).toBeUndefined();
|
|
90
|
-
expect(frame['workspace_id']).toBeUndefined();
|
|
91
|
-
});
|
|
92
|
-
it('omits lastEventId until something has actually been received', () => {
|
|
93
|
-
const c = connected();
|
|
94
|
-
c.subscribe(['conversation:1']);
|
|
95
|
-
const frame = emitted.find((e) => e.channel === 'subscribe')?.payload;
|
|
96
|
-
expect('lastEventId' in frame).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
it('replays from the last envelope id once one has arrived', () => {
|
|
99
|
-
// The cursor must be a real server-issued uuidv7 — the client used to
|
|
100
|
-
// mint its own id, which the server cannot resolve.
|
|
101
|
-
const c = connected();
|
|
102
|
-
const id = '01000000-0000-7000-8000-0000000000ab';
|
|
103
|
-
handlers.get('event')?.({
|
|
104
|
-
v: 1,
|
|
105
|
-
type: 'message.created',
|
|
106
|
-
id,
|
|
107
|
-
ts: Date.now(),
|
|
108
|
-
workspace_id: 'ws-1',
|
|
109
|
-
payload: {},
|
|
110
|
-
});
|
|
111
|
-
c.subscribe(['conversation:1']);
|
|
112
|
-
const frame = emitted.find((e) => e.channel === 'subscribe')?.payload;
|
|
113
|
-
expect(frame['lastEventId']).toBe(id);
|
|
114
|
-
});
|
|
115
|
-
it('re-subscribes on reconnect', () => {
|
|
116
|
-
const c = connected();
|
|
117
|
-
c.subscribe(['conversation:1']);
|
|
118
|
-
emitted.length = 0;
|
|
119
|
-
handlers.get('connect')?.(undefined);
|
|
120
|
-
expect(emitted.filter((e) => e.channel === 'subscribe')).toHaveLength(1);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
//# sourceMappingURL=RealtimeClient.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RealtimeClient.spec.js","sourceRoot":"","sources":["../../src/client/RealtimeClient.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RestClient.spec.d.ts","sourceRoot":"","sources":["../../src/client/RestClient.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stale-session recovery for the React Native SDK.
|
|
3
|
-
*
|
|
4
|
-
* When visitor tokens became signed envelopes, the server began refusing
|
|
5
|
-
* pre-signature tokens with a 403 carrying `visitor_session_upgrade_required`
|
|
6
|
-
* (or `visitor_session_invalid`). The browser widget and `@chatlychat/sdk-js`
|
|
7
|
-
* both recover from that in place; the mobile SDKs did not, so a mid-session
|
|
8
|
-
* visitor on an IDV-enabled channel got one hard failure and then kept
|
|
9
|
-
* failing, because the dead token stayed in AsyncStorage across launches.
|
|
10
|
-
*
|
|
11
|
-
* These pin the recovery to the widget's exact semantics:
|
|
12
|
-
* - only a 403, and only for `visitor_session_*` codes;
|
|
13
|
-
* - exactly one retry;
|
|
14
|
-
* - the replay re-inits with the REMEMBERED identity, so the contact — and
|
|
15
|
-
* therefore the conversation history — comes back;
|
|
16
|
-
* - the dead token is replaced in PERSISTENT storage, not just in memory;
|
|
17
|
-
* - the Idempotency-Key is stable across the replay.
|
|
18
|
-
*/
|
|
19
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
20
|
-
vi.mock('react-native', () => ({ Platform: { OS: 'ios' } }));
|
|
21
|
-
/**
|
|
22
|
-
* The REAL `@react-native-async-storage/async-storage` is used here, not a
|
|
23
|
-
* stub of `TokenStore`: outside a device its entry point is the web build,
|
|
24
|
-
* which is a thin shim over `window.localStorage`. Backing that with a Map
|
|
25
|
-
* exercises the whole persistence path the SDK actually runs — which is the
|
|
26
|
-
* point, since the bug being fixed is a dead token surviving on DISK.
|
|
27
|
-
*
|
|
28
|
-
* `TokenStore` loads the module with `require()`, which vite-node resolves
|
|
29
|
-
* outside the mock registry, so `vi.mock` on it would not take effect anyway.
|
|
30
|
-
*/
|
|
31
|
-
const store = new Map();
|
|
32
|
-
vi.stubGlobal('window', {
|
|
33
|
-
localStorage: {
|
|
34
|
-
getItem: (k) => store.get(k) ?? null,
|
|
35
|
-
setItem: (k, v) => {
|
|
36
|
-
store.set(k, v);
|
|
37
|
-
},
|
|
38
|
-
removeItem: (k) => {
|
|
39
|
-
store.delete(k);
|
|
40
|
-
},
|
|
41
|
-
clear: () => {
|
|
42
|
-
store.clear();
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
import { RestClient } from './RestClient';
|
|
47
|
-
const PUBLIC_ID = 'pub_test';
|
|
48
|
-
const TOKEN_KEY = `chatly:vt:${PUBLIC_ID}`;
|
|
49
|
-
const IDENTITY_KEY = `chatly:id:${PUBLIC_ID}`;
|
|
50
|
-
const calls = [];
|
|
51
|
-
function record(url, init) {
|
|
52
|
-
const headers = (init.headers ?? {});
|
|
53
|
-
const call = {
|
|
54
|
-
path: new URL(url).pathname,
|
|
55
|
-
body: JSON.parse(String(init.body)),
|
|
56
|
-
idempotencyKey: headers['idempotency-key'] ?? null,
|
|
57
|
-
};
|
|
58
|
-
calls.push(call);
|
|
59
|
-
return call;
|
|
60
|
-
}
|
|
61
|
-
function json(status, payload) {
|
|
62
|
-
return new Response(JSON.stringify(payload), {
|
|
63
|
-
status,
|
|
64
|
-
headers: { 'content-type': 'application/json' },
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function initPayload(token) {
|
|
68
|
-
return {
|
|
69
|
-
session: {
|
|
70
|
-
token,
|
|
71
|
-
contactId: 'c-1',
|
|
72
|
-
workspaceId: 'w-1',
|
|
73
|
-
brandId: 'b-1',
|
|
74
|
-
channelId: 'ch-1',
|
|
75
|
-
},
|
|
76
|
-
workspace: { id: 'w-1', name: 'Acme' },
|
|
77
|
-
branding: {},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
const IDENTITY = { externalId: 'u-42', userHash: 'deadbeef' };
|
|
81
|
-
function newClient() {
|
|
82
|
-
return new RestClient({ publicId: PUBLIC_ID, apiHost: 'api.test' });
|
|
83
|
-
}
|
|
84
|
-
/** Puts a (now stale) token and a remembered identity on "disk", exactly as a
|
|
85
|
-
* previous app launch would have left them. */
|
|
86
|
-
function seedDisk() {
|
|
87
|
-
store.set(TOKEN_KEY, 'stale-token');
|
|
88
|
-
store.set(IDENTITY_KEY, JSON.stringify(IDENTITY));
|
|
89
|
-
}
|
|
90
|
-
beforeEach(() => {
|
|
91
|
-
calls.length = 0;
|
|
92
|
-
store.clear();
|
|
93
|
-
});
|
|
94
|
-
describe('RestClient stale-session recovery', () => {
|
|
95
|
-
it('replays the call once after re-initing with the remembered identity', async () => {
|
|
96
|
-
seedDisk();
|
|
97
|
-
let repliesSeen = 0;
|
|
98
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
99
|
-
const call = record(url, init);
|
|
100
|
-
if (call.path === '/v1/widget/init') {
|
|
101
|
-
return Promise.resolve(json(200, initPayload('fresh-token')));
|
|
102
|
-
}
|
|
103
|
-
repliesSeen += 1;
|
|
104
|
-
if (repliesSeen === 1) {
|
|
105
|
-
return Promise.resolve(json(403, { code: 'visitor_session_upgrade_required' }));
|
|
106
|
-
}
|
|
107
|
-
return Promise.resolve(json(200, { message: { id: 'm-1' } }));
|
|
108
|
-
}));
|
|
109
|
-
const client = newClient();
|
|
110
|
-
await expect(client.reply('conv-1', 'hello')).resolves.toEqual({ message: { id: 'm-1' } });
|
|
111
|
-
// Exactly two calls to the original endpoint, one init wedged between.
|
|
112
|
-
expect(calls.map((c) => c.path)).toEqual([
|
|
113
|
-
'/v1/widget/messages',
|
|
114
|
-
'/v1/widget/init',
|
|
115
|
-
'/v1/widget/messages',
|
|
116
|
-
]);
|
|
117
|
-
// The re-init carried the remembered identity, so the replay lands on the
|
|
118
|
-
// same contact and the visitor's history survives.
|
|
119
|
-
expect(calls[1]?.body['identity']).toEqual(IDENTITY);
|
|
120
|
-
// ...and did NOT re-send the token the server just refused.
|
|
121
|
-
expect(calls[1]?.body['visitorToken']).toBeUndefined();
|
|
122
|
-
// The replay used the fresh token.
|
|
123
|
-
expect(calls[0]?.body['visitorToken']).toBe('stale-token');
|
|
124
|
-
expect(calls[2]?.body['visitorToken']).toBe('fresh-token');
|
|
125
|
-
// A replayed write must coalesce server-side, not double-send.
|
|
126
|
-
expect(calls[2]?.idempotencyKey).toBe(calls[0]?.idempotencyKey);
|
|
127
|
-
expect(calls[2]?.idempotencyKey).not.toBe(calls[1]?.idempotencyKey);
|
|
128
|
-
// Persisted, not just cached: the next launch must not repeat the failure.
|
|
129
|
-
expect(store.get(TOKEN_KEY)).toBe('fresh-token');
|
|
130
|
-
});
|
|
131
|
-
it('recovers `visitor_session_invalid` too, not just the upgrade code', async () => {
|
|
132
|
-
seedDisk();
|
|
133
|
-
let repliesSeen = 0;
|
|
134
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
135
|
-
const call = record(url, init);
|
|
136
|
-
if (call.path === '/v1/widget/init') {
|
|
137
|
-
return Promise.resolve(json(200, initPayload('fresh-token')));
|
|
138
|
-
}
|
|
139
|
-
repliesSeen += 1;
|
|
140
|
-
return repliesSeen === 1
|
|
141
|
-
? Promise.resolve(json(403, { code: 'visitor_session_invalid' }))
|
|
142
|
-
: Promise.resolve(json(200, { ok: true }));
|
|
143
|
-
}));
|
|
144
|
-
const client = newClient();
|
|
145
|
-
await expect(client.reply('conv-1', 'hello')).resolves.toEqual({ ok: true });
|
|
146
|
-
expect(calls.filter((c) => c.path === '/v1/widget/messages')).toHaveLength(2);
|
|
147
|
-
});
|
|
148
|
-
it('re-boots init in place when init itself is refused', async () => {
|
|
149
|
-
seedDisk();
|
|
150
|
-
let initsSeen = 0;
|
|
151
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
152
|
-
record(url, init);
|
|
153
|
-
initsSeen += 1;
|
|
154
|
-
return initsSeen === 1
|
|
155
|
-
? Promise.resolve(json(403, { code: 'visitor_session_invalid' }))
|
|
156
|
-
: Promise.resolve(json(200, initPayload('fresh-token')));
|
|
157
|
-
}));
|
|
158
|
-
const client = newClient();
|
|
159
|
-
await client.init();
|
|
160
|
-
expect(calls.map((c) => c.path)).toEqual(['/v1/widget/init', '/v1/widget/init']);
|
|
161
|
-
expect(calls[0]?.body['visitorToken']).toBe('stale-token');
|
|
162
|
-
expect(calls[1]?.body['visitorToken']).toBeUndefined();
|
|
163
|
-
expect(calls[1]?.body['identity']).toEqual(IDENTITY);
|
|
164
|
-
expect(store.get(TOKEN_KEY)).toBe('fresh-token');
|
|
165
|
-
});
|
|
166
|
-
it('does NOT retry a 403 with an unrelated code', async () => {
|
|
167
|
-
seedDisk();
|
|
168
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
169
|
-
record(url, init);
|
|
170
|
-
return Promise.resolve(json(403, { code: 'widget_origin_not_allowed' }));
|
|
171
|
-
}));
|
|
172
|
-
const client = newClient();
|
|
173
|
-
await expect(client.reply('conv-1', 'hello')).rejects.toThrow(/403/);
|
|
174
|
-
// One attempt, no init: retrying an origin refusal just fails again.
|
|
175
|
-
expect(calls.map((c) => c.path)).toEqual(['/v1/widget/messages']);
|
|
176
|
-
// The token was NOT thrown away — it was never the problem.
|
|
177
|
-
expect(store.get(TOKEN_KEY)).toBe('stale-token');
|
|
178
|
-
});
|
|
179
|
-
it('gives up after exactly one retry when the replay is refused too', async () => {
|
|
180
|
-
seedDisk();
|
|
181
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
182
|
-
const call = record(url, init);
|
|
183
|
-
return call.path === '/v1/widget/init'
|
|
184
|
-
? Promise.resolve(json(200, initPayload('fresh-token')))
|
|
185
|
-
: Promise.resolve(json(403, { code: 'visitor_session_invalid' }));
|
|
186
|
-
}));
|
|
187
|
-
const client = newClient();
|
|
188
|
-
await expect(client.reply('conv-1', 'hello')).rejects.toThrow(/403/);
|
|
189
|
-
expect(calls.filter((c) => c.path === '/v1/widget/messages')).toHaveLength(2);
|
|
190
|
-
});
|
|
191
|
-
it('does not retry a non-403 failure', async () => {
|
|
192
|
-
seedDisk();
|
|
193
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
194
|
-
record(url, init);
|
|
195
|
-
return Promise.resolve(json(500, { code: 'visitor_session_invalid' }));
|
|
196
|
-
}));
|
|
197
|
-
const client = newClient();
|
|
198
|
-
await expect(client.reply('conv-1', 'hello')).rejects.toThrow(/500/);
|
|
199
|
-
expect(calls).toHaveLength(1);
|
|
200
|
-
});
|
|
201
|
-
it('persists the identity only after the server accepts it', async () => {
|
|
202
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
203
|
-
record(url, init);
|
|
204
|
-
return Promise.resolve(json(200, initPayload('fresh-token')));
|
|
205
|
-
}));
|
|
206
|
-
const client = newClient();
|
|
207
|
-
expect(store.get(IDENTITY_KEY)).toBeUndefined();
|
|
208
|
-
await client.init(IDENTITY);
|
|
209
|
-
expect(JSON.parse(store.get(IDENTITY_KEY) ?? 'null')).toEqual(IDENTITY);
|
|
210
|
-
});
|
|
211
|
-
it('forgets the remembered identity on clearToken, so logout cannot be undone by a recovery', async () => {
|
|
212
|
-
seedDisk();
|
|
213
|
-
const client = newClient();
|
|
214
|
-
await client.clearToken();
|
|
215
|
-
expect(store.get(TOKEN_KEY)).toBeUndefined();
|
|
216
|
-
expect(store.get(IDENTITY_KEY)).toBeUndefined();
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
/**
|
|
220
|
-
* The envelope the API really sends.
|
|
221
|
-
*
|
|
222
|
-
* Every fixture above uses `{ code }` at the top level, and the API has never
|
|
223
|
-
* sent that: `app-exception.filter.ts` ends with `send({ error: body })`. So
|
|
224
|
-
* `isStaleSession` read `undefined`, returned false every time, and the
|
|
225
|
-
* recovery this whole file is about never fired in production — the suite was
|
|
226
|
-
* green because the fixtures agreed with the bug rather than with the server.
|
|
227
|
-
*
|
|
228
|
-
* Third place with the identical defect: the browser widget, `sdk-js`, and
|
|
229
|
-
* here. Each was written by reading the previous one, which is how a bug
|
|
230
|
-
* travels between SDKs that look independent.
|
|
231
|
-
*/
|
|
232
|
-
describe('the real error envelope', () => {
|
|
233
|
-
it('recovers a stale session from a NESTED 403', async () => {
|
|
234
|
-
seedDisk();
|
|
235
|
-
let repliesSeen = 0;
|
|
236
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
237
|
-
const call = record(url, init);
|
|
238
|
-
if (call.path === '/v1/widget/init') {
|
|
239
|
-
return Promise.resolve(json(200, initPayload('fresh-token')));
|
|
240
|
-
}
|
|
241
|
-
repliesSeen += 1;
|
|
242
|
-
if (repliesSeen === 1) {
|
|
243
|
-
return Promise.resolve(json(403, { error: { code: 'visitor_session_upgrade_required', message: 'stale' } }));
|
|
244
|
-
}
|
|
245
|
-
return Promise.resolve(json(200, { ok: true }));
|
|
246
|
-
}));
|
|
247
|
-
await newClient().listConversations();
|
|
248
|
-
// Refused once, re-initialised with the remembered identity, replayed.
|
|
249
|
-
expect(calls.filter((c) => c.path === '/v1/widget/conversations/list')).toHaveLength(2);
|
|
250
|
-
expect(calls.some((c) => c.path === '/v1/widget/init')).toBe(true);
|
|
251
|
-
});
|
|
252
|
-
it('still does NOT retry a nested non-session 403', async () => {
|
|
253
|
-
// An origin refusal fails identically on a retry; retrying is just
|
|
254
|
-
// slower. Reading the nested code must not make every 403 recoverable.
|
|
255
|
-
seedDisk();
|
|
256
|
-
vi.stubGlobal('fetch', vi.fn((url, init) => {
|
|
257
|
-
const call = record(url, init);
|
|
258
|
-
if (call.path === '/v1/widget/init') {
|
|
259
|
-
return Promise.resolve(json(200, initPayload('fresh-token')));
|
|
260
|
-
}
|
|
261
|
-
return Promise.resolve(json(403, { error: { code: 'forbidden', message: 'nope' } }));
|
|
262
|
-
}));
|
|
263
|
-
await expect(newClient().listConversations()).rejects.toThrow();
|
|
264
|
-
expect(calls.filter((c) => c.path === '/v1/widget/conversations/list')).toHaveLength(1);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
//# sourceMappingURL=RestClient.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RestClient.spec.js","sourceRoot":"","sources":["../../src/client/RestClient.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MessageBubble.spec.d.ts","sourceRoot":"","sources":["../../src/components/MessageBubble.spec.tsx"],"names":[],"mappings":""}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* The bubble, with rich blocks and a canvas MOUNTED.
|
|
4
|
-
*
|
|
5
|
-
* ── Why the mount is the thing under test ──────────────────────────────
|
|
6
|
-
*
|
|
7
|
-
* A renderer that handles every block type and that nothing mounts passes
|
|
8
|
-
* its own unit test and renders nowhere. That exact pair of defects shipped
|
|
9
|
-
* in this product once already: the agent inbox mounted blocks inside an
|
|
10
|
-
* `if (isNote)` early return so the AI answer the feature exists for never
|
|
11
|
-
* reached it, and the mobile renderer was exported, spec'd, and imported by
|
|
12
|
-
* nothing but its own spec. `check-block-renderer-parity.mjs` asserts the
|
|
13
|
-
* mount literal is in this file and that THIS spec carries a block envelope;
|
|
14
|
-
* it cannot tell which branches a component has, so the assertions below are
|
|
15
|
-
* the half that reads the output.
|
|
16
|
-
*
|
|
17
|
-
* The interactive half matters as much. On the agent app every action is
|
|
18
|
-
* plain text, because an agent has no visitor session; here a tap must reach
|
|
19
|
-
* either the API or the integrator's own code, and `host_event` reaching the
|
|
20
|
-
* app is the single feature this package exists for.
|
|
21
|
-
*/
|
|
22
|
-
import { cleanup, render, screen } from '@testing-library/react';
|
|
23
|
-
import userEvent from '@testing-library/user-event';
|
|
24
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
25
|
-
import { ChatlyContext } from '../ChatlyProvider';
|
|
26
|
-
import { MessageBubble } from './MessageBubble';
|
|
27
|
-
import { defaultTheme } from '../theme/defaultTheme';
|
|
28
|
-
const CONV = 'conv-1';
|
|
29
|
-
/*
|
|
30
|
-
* Explicit, not inherited.
|
|
31
|
-
*
|
|
32
|
-
* `@testing-library/react` auto-cleans only when vitest runs with
|
|
33
|
-
* `globals: true`, which this package does not. Without it each render
|
|
34
|
-
* accumulates in the same document and `getByText` starts failing with
|
|
35
|
-
* "found multiple elements" — which reads as a duplicate-render bug in the
|
|
36
|
-
* component rather than as a missing afterEach. Three tests failed that way
|
|
37
|
-
* before this line existed.
|
|
38
|
-
*/
|
|
39
|
-
afterEach(cleanup);
|
|
40
|
-
function harness(over = {}) {
|
|
41
|
-
const rest = {
|
|
42
|
-
reply: vi.fn().mockResolvedValue({}),
|
|
43
|
-
postback: vi.fn().mockResolvedValue({}),
|
|
44
|
-
reportAction: vi.fn().mockResolvedValue({}),
|
|
45
|
-
requestCanvasStep: vi.fn().mockResolvedValue({}),
|
|
46
|
-
submitCanvas: vi.fn().mockResolvedValue({ canvas: null, errors: {} }),
|
|
47
|
-
};
|
|
48
|
-
const value = {
|
|
49
|
-
theme: defaultTheme,
|
|
50
|
-
rest,
|
|
51
|
-
...over,
|
|
52
|
-
};
|
|
53
|
-
const wrap = (message) => render(_jsx(ChatlyContext.Provider, { value: value, children: _jsx(MessageBubble, { message: message, conversationId: CONV }) }));
|
|
54
|
-
return { rest, wrap };
|
|
55
|
-
}
|
|
56
|
-
const row = (over) => ({
|
|
57
|
-
id: 'm1',
|
|
58
|
-
authorType: 'bot',
|
|
59
|
-
kind: 'text',
|
|
60
|
-
body: 'Here are six hats',
|
|
61
|
-
createdAt: '2026-10-12T00:00:00.000Z',
|
|
62
|
-
...over,
|
|
63
|
-
});
|
|
64
|
-
describe('MessageBubble + blocks', () => {
|
|
65
|
-
it('renders the body and the card beside it', () => {
|
|
66
|
-
const h = harness();
|
|
67
|
-
h.wrap(row({
|
|
68
|
-
blocks: {
|
|
69
|
-
v: 1,
|
|
70
|
-
blocks: [{ type: 'card', title: 'Wool hat', price: { amount: 4820, currency: 'USD' } }],
|
|
71
|
-
},
|
|
72
|
-
}));
|
|
73
|
-
expect(screen.getByText('Here are six hats')).toBeDefined();
|
|
74
|
-
expect(screen.getByText('Wool hat')).toBeDefined();
|
|
75
|
-
// Through the shared formatter: minor units, and the divisor read off the
|
|
76
|
-
// currency rather than assumed to be 100.
|
|
77
|
-
expect(screen.getByText('$48.20')).toBeDefined();
|
|
78
|
-
});
|
|
79
|
-
it('shows a was-price as words, not as a strike-through', () => {
|
|
80
|
-
// RN has no `<s>` and `textDecorationLine` on a nested `Text` is
|
|
81
|
-
// unreliable across the two platforms; "was" reads in a screen reader too.
|
|
82
|
-
const h = harness();
|
|
83
|
-
h.wrap(row({
|
|
84
|
-
blocks: {
|
|
85
|
-
v: 1,
|
|
86
|
-
blocks: [
|
|
87
|
-
{
|
|
88
|
-
type: 'card',
|
|
89
|
-
title: 'Wool hat',
|
|
90
|
-
price: { amount: 4820, currency: 'USD', compareAt: 6000 },
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
}));
|
|
95
|
-
expect(screen.getByText('$48.20 (was $60.00)')).toBeDefined();
|
|
96
|
-
});
|
|
97
|
-
it('hands a host_event to the APP and calls no API', async () => {
|
|
98
|
-
// The feature this package exists for: a native payment sheet cannot be
|
|
99
|
-
// opened from inside a chat library.
|
|
100
|
-
const onAction = vi.fn();
|
|
101
|
-
const h = harness({ onAction });
|
|
102
|
-
h.wrap(row({
|
|
103
|
-
blocks: {
|
|
104
|
-
v: 1,
|
|
105
|
-
blocks: [
|
|
106
|
-
{
|
|
107
|
-
type: 'actions',
|
|
108
|
-
actions: [
|
|
109
|
-
{ kind: 'host_event', label: 'Pay', event: 'pay', id: 'a1', data: { x: 1 } },
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
},
|
|
114
|
-
}));
|
|
115
|
-
await userEvent.click(screen.getByRole('button', { name: 'Pay' }));
|
|
116
|
-
expect(onAction).toHaveBeenCalledWith({
|
|
117
|
-
event: 'pay',
|
|
118
|
-
data: { x: 1 },
|
|
119
|
-
conversationId: CONV,
|
|
120
|
-
messageId: 'm1',
|
|
121
|
-
actionId: 'a1',
|
|
122
|
-
});
|
|
123
|
-
expect(h.rest.reply).not.toHaveBeenCalled();
|
|
124
|
-
expect(h.rest.postback).not.toHaveBeenCalled();
|
|
125
|
-
});
|
|
126
|
-
it('sends a postback through the API, silently when asked', async () => {
|
|
127
|
-
const h = harness();
|
|
128
|
-
h.wrap(row({
|
|
129
|
-
blocks: {
|
|
130
|
-
v: 1,
|
|
131
|
-
blocks: [
|
|
132
|
-
{
|
|
133
|
-
type: 'actions',
|
|
134
|
-
actions: [{ kind: 'postback', label: 'Under $50', value: 'under_50', silent: true }],
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
},
|
|
138
|
-
}));
|
|
139
|
-
await userEvent.click(screen.getByRole('button', { name: 'Under $50' }));
|
|
140
|
-
expect(h.rest.postback).toHaveBeenCalledWith(CONV, { value: 'under_50', silent: true });
|
|
141
|
-
});
|
|
142
|
-
it('sends a quick reply as an ordinary message', async () => {
|
|
143
|
-
const h = harness();
|
|
144
|
-
h.wrap(row({
|
|
145
|
-
blocks: {
|
|
146
|
-
v: 1,
|
|
147
|
-
blocks: [{ type: 'quick_replies', options: [{ label: 'Under $50', value: 'under_50' }] }],
|
|
148
|
-
},
|
|
149
|
-
}));
|
|
150
|
-
await userEvent.click(screen.getByRole('button', { name: 'Under $50' }));
|
|
151
|
-
expect(h.rest.reply).toHaveBeenCalledWith(CONV, 'under_50');
|
|
152
|
-
});
|
|
153
|
-
it('renders a carousel as a scrollable row of every card', () => {
|
|
154
|
-
const h = harness();
|
|
155
|
-
h.wrap(row({
|
|
156
|
-
blocks: {
|
|
157
|
-
v: 1,
|
|
158
|
-
blocks: [
|
|
159
|
-
{
|
|
160
|
-
type: 'card_grid',
|
|
161
|
-
layout: 'carousel',
|
|
162
|
-
cards: [{ title: 'One' }, { title: 'Two' }, { title: 'Three' }],
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
},
|
|
166
|
-
}));
|
|
167
|
-
expect(screen.getByLabelText('Scrollable options')).toBeDefined();
|
|
168
|
-
for (const title of ['One', 'Two', 'Three']) {
|
|
169
|
-
expect(screen.getByText(title)).toBeDefined();
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
it('renders a status line — unlike the agent app, which skips it', () => {
|
|
173
|
-
// There it is "progress, not content" for somebody reading history. Here
|
|
174
|
-
// the shopper is watching it happen.
|
|
175
|
-
const h = harness();
|
|
176
|
-
h.wrap(row({ blocks: { v: 1, blocks: [{ type: 'status', id: 's1', text: 'Looking…' }] } }));
|
|
177
|
-
expect(screen.getByText('Looking…')).toBeDefined();
|
|
178
|
-
});
|
|
179
|
-
it('drops an unsafe card image and keeps the card', () => {
|
|
180
|
-
const h = harness();
|
|
181
|
-
h.wrap(row({
|
|
182
|
-
blocks: {
|
|
183
|
-
v: 1,
|
|
184
|
-
blocks: [{ type: 'card', title: 'Wool hat', image: 'javascript:alert(1)' }],
|
|
185
|
-
},
|
|
186
|
-
}));
|
|
187
|
-
expect(screen.getByText('Wool hat')).toBeDefined();
|
|
188
|
-
expect(screen.queryByRole('img')).toBeNull();
|
|
189
|
-
});
|
|
190
|
-
it('renders nothing extra for a message with no blocks', () => {
|
|
191
|
-
const h = harness();
|
|
192
|
-
h.wrap(row({}));
|
|
193
|
-
expect(screen.getByText('Here are six hats')).toBeDefined();
|
|
194
|
-
expect(screen.queryByRole('button')).toBeNull();
|
|
195
|
-
});
|
|
196
|
-
it('renders a system line WITHOUT blocks — a system message is never a card', () => {
|
|
197
|
-
const h = harness();
|
|
198
|
-
h.wrap(row({
|
|
199
|
-
authorType: 'system',
|
|
200
|
-
body: 'Buyer support is taking over from here.',
|
|
201
|
-
blocks: { v: 1, blocks: [{ type: 'card', title: 'Should not render' }] },
|
|
202
|
-
}));
|
|
203
|
-
expect(screen.getByText('Buyer support is taking over from here.')).toBeDefined();
|
|
204
|
-
expect(screen.queryByText('Should not render')).toBeNull();
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
//# sourceMappingURL=MessageBubble.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MessageBubble.spec.js","sourceRoot":"","sources":["../../src/components/MessageBubble.spec.tsx"],"names":[],"mappings":"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"}
|