@proveanything/smartlinks 1.8.0 → 1.8.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/dist/api/ai.js +3 -99
- package/dist/api/analytics.d.ts +3 -3
- package/dist/api/analytics.js +48 -11
- package/dist/docs/API_SUMMARY.md +50 -8
- package/dist/docs/analytics-metadata-conventions.md +18 -14
- package/dist/docs/analytics.md +14 -17
- package/dist/docs/iframe-streaming-parent-changes.md +308 -0
- package/dist/http.d.ts +9 -0
- package/dist/http.js +236 -0
- package/dist/iframeResponder.d.ts +4 -0
- package/dist/iframeResponder.js +152 -0
- package/dist/openapi.yaml +68 -5
- package/dist/types/analytics.d.ts +10 -9
- package/dist/types/iframeResponder.d.ts +20 -0
- package/docs/API_SUMMARY.md +50 -8
- package/docs/analytics-metadata-conventions.md +18 -14
- package/docs/analytics.md +14 -17
- package/docs/iframe-streaming-parent-changes.md +308 -0
- package/openapi.yaml +68 -5
- package/package.json +1 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Iframe Streaming Parent Changes
|
|
2
|
+
|
|
3
|
+
This note describes the parent-side changes needed to support AI streaming when an embedded SmartLinks app is running in iframe proxy mode.
|
|
4
|
+
|
|
5
|
+
If you are using the SDK `IframeResponder` directly, this is already implemented in the SDK changes. You only need this document if your parent application has its own iframe proxy handler and does not rely on `IframeResponder`.
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
Keep the existing architecture:
|
|
10
|
+
|
|
11
|
+
- local mode: child calls API directly
|
|
12
|
+
- iframe proxy mode: child never owns auth state and streams through the parent
|
|
13
|
+
|
|
14
|
+
This keeps user/session authority in the parent while making AI streaming behave like the rest of the SDK transport.
|
|
15
|
+
|
|
16
|
+
## What changed
|
|
17
|
+
|
|
18
|
+
Previously, proxy mode only supported one-shot request/response messages:
|
|
19
|
+
|
|
20
|
+
- `_smartlinksProxyRequest`
|
|
21
|
+
- `_smartlinksProxyResponse`
|
|
22
|
+
|
|
23
|
+
Streaming now adds a second protocol for long-lived responses:
|
|
24
|
+
|
|
25
|
+
- `_smartlinksProxyStreamRequest`
|
|
26
|
+
- `_smartlinksProxyStream`
|
|
27
|
+
- `_smartlinksProxyStreamAbort`
|
|
28
|
+
|
|
29
|
+
## New parent message handling
|
|
30
|
+
|
|
31
|
+
### 1. Listen for stream requests
|
|
32
|
+
|
|
33
|
+
The iframe child may now send this message:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
{
|
|
37
|
+
_smartlinksProxyStreamRequest: true,
|
|
38
|
+
id: string,
|
|
39
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
|
40
|
+
path: string,
|
|
41
|
+
body?: any,
|
|
42
|
+
headers?: Record<string, string>
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Parent behavior:
|
|
47
|
+
|
|
48
|
+
- treat this like a proxied API request
|
|
49
|
+
- build the real API URL from your configured base URL plus `path`
|
|
50
|
+
- send the request using the parent's current auth/session context
|
|
51
|
+
- expect an SSE / streaming response body
|
|
52
|
+
- keep the request open until the stream ends or is aborted
|
|
53
|
+
|
|
54
|
+
### 2. Forward stream lifecycle messages back to the child
|
|
55
|
+
|
|
56
|
+
The parent should send messages back to the iframe using this envelope:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
{
|
|
60
|
+
_smartlinksProxyStream: true,
|
|
61
|
+
id: string,
|
|
62
|
+
phase: 'open' | 'event' | 'end' | 'error',
|
|
63
|
+
data?: any,
|
|
64
|
+
error?: string,
|
|
65
|
+
status?: number
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Phases:
|
|
70
|
+
|
|
71
|
+
- `open`
|
|
72
|
+
- optional but recommended
|
|
73
|
+
- indicates the upstream streaming request was accepted and a body exists
|
|
74
|
+
- `event`
|
|
75
|
+
- contains one parsed JSON event from an SSE `data:` frame
|
|
76
|
+
- send one message per logical event payload
|
|
77
|
+
- `end`
|
|
78
|
+
- sent once when the stream finishes normally
|
|
79
|
+
- `error`
|
|
80
|
+
- sent if the upstream request fails before or during streaming
|
|
81
|
+
|
|
82
|
+
### 3. Support abort from the child
|
|
83
|
+
|
|
84
|
+
The child may stop reading early and send:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
{
|
|
88
|
+
_smartlinksProxyStreamAbort: true,
|
|
89
|
+
id: string
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Parent behavior:
|
|
94
|
+
|
|
95
|
+
- look up the active stream by `id`
|
|
96
|
+
- abort the underlying fetch / reader
|
|
97
|
+
- clean up any local state for that stream
|
|
98
|
+
- do not keep streaming after abort
|
|
99
|
+
|
|
100
|
+
## SSE forwarding rules
|
|
101
|
+
|
|
102
|
+
The upstream AI endpoints return SSE-like frames. The parent should:
|
|
103
|
+
|
|
104
|
+
- read the response body as a stream
|
|
105
|
+
- buffer text until line boundaries
|
|
106
|
+
- collect `data:` lines for a single event
|
|
107
|
+
- join multi-line `data:` payloads with `\n`
|
|
108
|
+
- ignore blank events
|
|
109
|
+
- stop on `data: [DONE]`
|
|
110
|
+
- JSON-parse each event payload
|
|
111
|
+
- forward parsed payloads to the iframe as `_smartlinksProxyStream` with `phase: 'event'`
|
|
112
|
+
|
|
113
|
+
Minimal parsing behavior:
|
|
114
|
+
|
|
115
|
+
1. accumulate bytes into text
|
|
116
|
+
2. split on `\r?\n`
|
|
117
|
+
3. collect each `data:` line
|
|
118
|
+
4. on blank line, finalize the event
|
|
119
|
+
5. if payload is `[DONE]`, finish
|
|
120
|
+
6. otherwise `JSON.parse(payload)` and forward
|
|
121
|
+
|
|
122
|
+
## Auth and session expectations
|
|
123
|
+
|
|
124
|
+
The parent remains the source of truth for auth.
|
|
125
|
+
|
|
126
|
+
That means the parent stream handler should:
|
|
127
|
+
|
|
128
|
+
- use the same auth headers/token source as normal proxied requests
|
|
129
|
+
- not require the iframe to know the bearer token or API key
|
|
130
|
+
- naturally pick up the current logged-in user when the stream starts
|
|
131
|
+
- cancel active streams if your app invalidates session state on logout or account switch
|
|
132
|
+
|
|
133
|
+
In practice, the stream request should use the same header-building logic as your normal parent proxy transport.
|
|
134
|
+
|
|
135
|
+
## Error handling expectations
|
|
136
|
+
|
|
137
|
+
If the upstream fetch returns a non-2xx status:
|
|
138
|
+
|
|
139
|
+
- try to read the JSON error body
|
|
140
|
+
- derive a useful message
|
|
141
|
+
- send one `_smartlinksProxyStream` message with `phase: 'error'`
|
|
142
|
+
- include `status` when available
|
|
143
|
+
- do not send `end` afterward
|
|
144
|
+
|
|
145
|
+
If the stream body is missing unexpectedly:
|
|
146
|
+
|
|
147
|
+
- send `phase: 'error'`
|
|
148
|
+
|
|
149
|
+
If JSON parsing fails for a single event chunk:
|
|
150
|
+
|
|
151
|
+
- safest behavior is to ignore that malformed chunk and continue
|
|
152
|
+
|
|
153
|
+
## State the parent should keep
|
|
154
|
+
|
|
155
|
+
Track active streams in a map keyed by `id`:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
Map<string, AbortController>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Recommended cleanup points:
|
|
162
|
+
|
|
163
|
+
- on normal stream end
|
|
164
|
+
- on error
|
|
165
|
+
- on child abort
|
|
166
|
+
- on iframe detach/unmount
|
|
167
|
+
- on parent auth reset/logout if you want all in-flight streams cancelled immediately
|
|
168
|
+
|
|
169
|
+
## Parent implementation outline
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const activeStreams = new Map<string, AbortController>()
|
|
173
|
+
|
|
174
|
+
window.addEventListener('message', async (event) => {
|
|
175
|
+
const msg = event.data
|
|
176
|
+
|
|
177
|
+
if (msg?._smartlinksProxyStreamAbort && msg.id) {
|
|
178
|
+
activeStreams.get(msg.id)?.abort()
|
|
179
|
+
activeStreams.delete(msg.id)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (msg?._smartlinksProxyStreamRequest && msg.id) {
|
|
184
|
+
const controller = new AbortController()
|
|
185
|
+
activeStreams.set(msg.id, controller)
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch(buildUrl(msg.path), {
|
|
189
|
+
method: msg.method,
|
|
190
|
+
headers: msg.headers,
|
|
191
|
+
body: msg.body ? JSON.stringify(msg.body) : undefined,
|
|
192
|
+
signal: controller.signal,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
if (!response.ok || !response.body) {
|
|
196
|
+
postError(...)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
postOpen(...)
|
|
201
|
+
await forwardSse(response.body, parsed => postEvent(...parsed))
|
|
202
|
+
postEnd(...)
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (err?.name !== 'AbortError') postError(...)
|
|
205
|
+
} finally {
|
|
206
|
+
activeStreams.delete(msg.id)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Exact protocol summary
|
|
213
|
+
|
|
214
|
+
### Child → parent
|
|
215
|
+
|
|
216
|
+
Standard stream request:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
{
|
|
220
|
+
_smartlinksProxyStreamRequest: true,
|
|
221
|
+
id,
|
|
222
|
+
method,
|
|
223
|
+
path,
|
|
224
|
+
body,
|
|
225
|
+
headers
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Abort request:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
{
|
|
233
|
+
_smartlinksProxyStreamAbort: true,
|
|
234
|
+
id
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Parent → child
|
|
239
|
+
|
|
240
|
+
Open:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
{
|
|
244
|
+
_smartlinksProxyStream: true,
|
|
245
|
+
id,
|
|
246
|
+
phase: 'open'
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Event:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
{
|
|
254
|
+
_smartlinksProxyStream: true,
|
|
255
|
+
id,
|
|
256
|
+
phase: 'event',
|
|
257
|
+
data: parsedJsonEvent
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
End:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
{
|
|
265
|
+
_smartlinksProxyStream: true,
|
|
266
|
+
id,
|
|
267
|
+
phase: 'end'
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Error:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
{
|
|
275
|
+
_smartlinksProxyStream: true,
|
|
276
|
+
id,
|
|
277
|
+
phase: 'error',
|
|
278
|
+
error: 'message',
|
|
279
|
+
status?: number
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## What does not change
|
|
284
|
+
|
|
285
|
+
These parts of the parent iframe integration stay the same:
|
|
286
|
+
|
|
287
|
+
- normal `_smartlinksProxyRequest` request/response flow
|
|
288
|
+
- upload proxy flow
|
|
289
|
+
- auth login/logout postMessage handling
|
|
290
|
+
- route/deep-link handling
|
|
291
|
+
- resize handling
|
|
292
|
+
|
|
293
|
+
This is an additive protocol, not a replacement.
|
|
294
|
+
|
|
295
|
+
## Current SDK reference
|
|
296
|
+
|
|
297
|
+
The SDK implementation lives in:
|
|
298
|
+
|
|
299
|
+
- [src/http.ts](src/http.ts)
|
|
300
|
+
- [src/iframeResponder.ts](src/iframeResponder.ts)
|
|
301
|
+
- [src/types/iframeResponder.ts](src/types/iframeResponder.ts)
|
|
302
|
+
- [src/api/ai.ts](src/api/ai.ts)
|
|
303
|
+
|
|
304
|
+
## Practical recommendation
|
|
305
|
+
|
|
306
|
+
If your parent already uses `IframeResponder`, prefer upgrading to the SDK version with these changes instead of re-implementing the protocol manually.
|
|
307
|
+
|
|
308
|
+
If your parent has a custom iframe bridge, implement exactly the three new message types above and reuse your existing auth/header logic from normal proxied requests.
|
package/dist/http.d.ts
CHANGED
|
@@ -167,6 +167,15 @@ export declare function patch<T>(path: string, body: any, extraHeaders?: Record<
|
|
|
167
167
|
* Returns the parsed JSON as T, or throws an Error.
|
|
168
168
|
*/
|
|
169
169
|
export declare function requestWithOptions<T>(path: string, options: RequestInit): Promise<T>;
|
|
170
|
+
/**
|
|
171
|
+
* Internal helper that performs a streaming request using the shared auth and proxy transport.
|
|
172
|
+
* The response is expected to be `text/event-stream` with JSON payloads in `data:` frames.
|
|
173
|
+
*/
|
|
174
|
+
export declare function requestStream<T>(path: string, options?: {
|
|
175
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
176
|
+
body?: any;
|
|
177
|
+
headers?: Record<string, string>;
|
|
178
|
+
}): Promise<AsyncIterable<T>>;
|
|
170
179
|
/**
|
|
171
180
|
* Internal helper that performs a DELETE request to `${baseURL}${path}`,
|
|
172
181
|
* injecting headers for apiKey or bearerToken if present.
|
package/dist/http.js
CHANGED
|
@@ -13,6 +13,18 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
13
13
|
}
|
|
14
14
|
return t;
|
|
15
15
|
};
|
|
16
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
17
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
18
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
19
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
20
|
+
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
21
|
+
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
|
22
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
23
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
24
|
+
function fulfill(value) { resume("next", value); }
|
|
25
|
+
function reject(value) { resume("throw", value); }
|
|
26
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
27
|
+
};
|
|
16
28
|
import { SmartlinksApiError, SmartlinksOfflineError } from "./types/error";
|
|
17
29
|
import { idbGet, idbSet, idbClear } from './persistentCache';
|
|
18
30
|
let baseURL = null;
|
|
@@ -514,15 +526,178 @@ export function invalidateCache(urlPattern) {
|
|
|
514
526
|
}
|
|
515
527
|
// Map of pending proxy requests: id -> {resolve, reject}
|
|
516
528
|
const proxyPending = {};
|
|
529
|
+
const proxyStreamPending = new Map();
|
|
517
530
|
function generateProxyId() {
|
|
518
531
|
return "proxy_" + Math.random().toString(36).slice(2) + Date.now();
|
|
519
532
|
}
|
|
533
|
+
async function createFetchError(response, url) {
|
|
534
|
+
let responseBody;
|
|
535
|
+
try {
|
|
536
|
+
responseBody = await response.json();
|
|
537
|
+
}
|
|
538
|
+
catch (_a) {
|
|
539
|
+
responseBody = null;
|
|
540
|
+
}
|
|
541
|
+
const errBody = normalizeErrorResponse(responseBody, response.status);
|
|
542
|
+
return new SmartlinksApiError(`Error ${errBody.code}: ${errBody.message}`, response.status, errBody, url);
|
|
543
|
+
}
|
|
544
|
+
function createProxyStreamIterable(id) {
|
|
545
|
+
const queue = [];
|
|
546
|
+
const waiters = [];
|
|
547
|
+
let done = false;
|
|
548
|
+
let failure = null;
|
|
549
|
+
const flushDone = () => {
|
|
550
|
+
while (waiters.length) {
|
|
551
|
+
const waiter = waiters.shift();
|
|
552
|
+
waiter === null || waiter === void 0 ? void 0 : waiter.resolve({ value: undefined, done: true });
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const flushError = (error) => {
|
|
556
|
+
while (waiters.length) {
|
|
557
|
+
const waiter = waiters.shift();
|
|
558
|
+
waiter === null || waiter === void 0 ? void 0 : waiter.reject(error);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const iterator = {
|
|
562
|
+
next() {
|
|
563
|
+
if (queue.length) {
|
|
564
|
+
return Promise.resolve({ value: queue.shift(), done: false });
|
|
565
|
+
}
|
|
566
|
+
if (failure) {
|
|
567
|
+
return Promise.reject(failure);
|
|
568
|
+
}
|
|
569
|
+
if (done) {
|
|
570
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
571
|
+
}
|
|
572
|
+
return new Promise((resolve, reject) => {
|
|
573
|
+
waiters.push({ resolve, reject });
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
async return() {
|
|
577
|
+
if (!done && !failure) {
|
|
578
|
+
done = true;
|
|
579
|
+
proxyStreamPending.delete(id);
|
|
580
|
+
try {
|
|
581
|
+
window.parent.postMessage({ _smartlinksProxyStreamAbort: true, id }, '*');
|
|
582
|
+
}
|
|
583
|
+
catch (_a) { }
|
|
584
|
+
}
|
|
585
|
+
flushDone();
|
|
586
|
+
return { value: undefined, done: true };
|
|
587
|
+
},
|
|
588
|
+
async throw(error) {
|
|
589
|
+
failure = error || new Error('Proxy stream failed');
|
|
590
|
+
done = true;
|
|
591
|
+
proxyStreamPending.delete(id);
|
|
592
|
+
flushError(failure);
|
|
593
|
+
throw failure;
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
push(value) {
|
|
598
|
+
if (done || failure)
|
|
599
|
+
return;
|
|
600
|
+
const waiter = waiters.shift();
|
|
601
|
+
if (waiter) {
|
|
602
|
+
waiter.resolve({ value, done: false });
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
queue.push(value);
|
|
606
|
+
},
|
|
607
|
+
finish() {
|
|
608
|
+
if (done || failure)
|
|
609
|
+
return;
|
|
610
|
+
done = true;
|
|
611
|
+
proxyStreamPending.delete(id);
|
|
612
|
+
flushDone();
|
|
613
|
+
},
|
|
614
|
+
fail(error) {
|
|
615
|
+
if (done || failure)
|
|
616
|
+
return;
|
|
617
|
+
failure = error;
|
|
618
|
+
done = true;
|
|
619
|
+
proxyStreamPending.delete(id);
|
|
620
|
+
flushError(error);
|
|
621
|
+
},
|
|
622
|
+
iterable: {
|
|
623
|
+
[Symbol.asyncIterator]() {
|
|
624
|
+
return iterator;
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function parseSsePayload(payload) {
|
|
630
|
+
if (!payload || payload === '[DONE]')
|
|
631
|
+
return undefined;
|
|
632
|
+
try {
|
|
633
|
+
return JSON.parse(payload);
|
|
634
|
+
}
|
|
635
|
+
catch (_a) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function parseSseStream(stream) {
|
|
640
|
+
return __asyncGenerator(this, arguments, function* parseSseStream_1() {
|
|
641
|
+
const reader = stream.getReader();
|
|
642
|
+
const decoder = new TextDecoder();
|
|
643
|
+
let buffer = '';
|
|
644
|
+
let dataLines = [];
|
|
645
|
+
while (true) {
|
|
646
|
+
const { done, value } = yield __await(reader.read());
|
|
647
|
+
if (done)
|
|
648
|
+
break;
|
|
649
|
+
buffer += decoder.decode(value, { stream: true });
|
|
650
|
+
const lines = buffer.split(/\r?\n/);
|
|
651
|
+
buffer = lines.pop() || '';
|
|
652
|
+
for (const rawLine of lines) {
|
|
653
|
+
const line = rawLine.trimEnd();
|
|
654
|
+
if (!line) {
|
|
655
|
+
if (!dataLines.length)
|
|
656
|
+
continue;
|
|
657
|
+
const parsed = parseSsePayload(dataLines.join('\n'));
|
|
658
|
+
dataLines = [];
|
|
659
|
+
if (parsed !== undefined) {
|
|
660
|
+
yield yield __await(parsed);
|
|
661
|
+
}
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (line.startsWith('data:')) {
|
|
665
|
+
dataLines.push(line.slice(5).trimStart());
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (dataLines.length) {
|
|
670
|
+
const parsed = parseSsePayload(dataLines.join('\n'));
|
|
671
|
+
if (parsed !== undefined) {
|
|
672
|
+
yield yield __await(parsed);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
520
677
|
// Shared listener for proxy responses
|
|
521
678
|
function ensureProxyListener() {
|
|
522
679
|
if (window._smartlinksProxyListener)
|
|
523
680
|
return;
|
|
524
681
|
window.addEventListener("message", (event) => {
|
|
525
682
|
const msg = event.data;
|
|
683
|
+
if ((msg === null || msg === void 0 ? void 0 : msg._smartlinksProxyStream) && msg.id) {
|
|
684
|
+
const pendingStream = proxyStreamPending.get(msg.id);
|
|
685
|
+
if (!pendingStream)
|
|
686
|
+
return;
|
|
687
|
+
if (msg.phase === 'event') {
|
|
688
|
+
pendingStream.push(msg.data);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (msg.phase === 'end') {
|
|
692
|
+
pendingStream.finish();
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (msg.phase === 'error') {
|
|
696
|
+
pendingStream.fail(new Error(msg.error || 'Proxy stream failed'));
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
526
701
|
if (!msg || !msg._smartlinksProxyResponse || !msg.id)
|
|
527
702
|
return;
|
|
528
703
|
logDebug('[smartlinks] proxy:response', { id: msg.id, ok: !msg.error, keys: Object.keys(msg) });
|
|
@@ -539,6 +714,32 @@ function ensureProxyListener() {
|
|
|
539
714
|
});
|
|
540
715
|
window._smartlinksProxyListener = true;
|
|
541
716
|
}
|
|
717
|
+
async function proxyStreamRequest(method, path, body, headers) {
|
|
718
|
+
ensureProxyListener();
|
|
719
|
+
const payloadBody = (typeof FormData !== 'undefined' && body instanceof FormData)
|
|
720
|
+
? serializeFormDataForProxy(body)
|
|
721
|
+
: body;
|
|
722
|
+
const id = generateProxyId();
|
|
723
|
+
const streamController = createProxyStreamIterable(id);
|
|
724
|
+
proxyStreamPending.set(id, streamController);
|
|
725
|
+
const msg = {
|
|
726
|
+
_smartlinksProxyStreamRequest: true,
|
|
727
|
+
id,
|
|
728
|
+
method,
|
|
729
|
+
path,
|
|
730
|
+
body: payloadBody,
|
|
731
|
+
headers,
|
|
732
|
+
};
|
|
733
|
+
logDebug('[smartlinks] proxy:stream postMessage', {
|
|
734
|
+
id,
|
|
735
|
+
method,
|
|
736
|
+
path,
|
|
737
|
+
headers: headers ? redactHeaders(headers) : undefined,
|
|
738
|
+
hasBody: !!body,
|
|
739
|
+
});
|
|
740
|
+
window.parent.postMessage(msg, '*');
|
|
741
|
+
return streamController.iterable;
|
|
742
|
+
}
|
|
542
743
|
// Proxy request implementation
|
|
543
744
|
function serializeFormDataForProxy(fd) {
|
|
544
745
|
const entries = [];
|
|
@@ -1105,6 +1306,41 @@ export async function requestWithOptions(path, options) {
|
|
|
1105
1306
|
}
|
|
1106
1307
|
return fetchPromise;
|
|
1107
1308
|
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Internal helper that performs a streaming request using the shared auth and proxy transport.
|
|
1311
|
+
* The response is expected to be `text/event-stream` with JSON payloads in `data:` frames.
|
|
1312
|
+
*/
|
|
1313
|
+
export async function requestStream(path, options) {
|
|
1314
|
+
const method = (options === null || options === void 0 ? void 0 : options.method) || 'POST';
|
|
1315
|
+
const body = options === null || options === void 0 ? void 0 : options.body;
|
|
1316
|
+
const extraHeaders = (options === null || options === void 0 ? void 0 : options.headers) || {};
|
|
1317
|
+
const headers = Object.assign(Object.assign(Object.assign({}, extraHeaders), getApiHeaders()), { Accept: 'text/event-stream' });
|
|
1318
|
+
if (!(typeof FormData !== 'undefined' && body instanceof FormData) && body !== undefined && !Object.keys(headers).some(k => k.toLowerCase() === 'content-type')) {
|
|
1319
|
+
headers['Content-Type'] = 'application/json';
|
|
1320
|
+
}
|
|
1321
|
+
if (proxyMode) {
|
|
1322
|
+
logDebug('[smartlinks] stream via proxy', { path, method });
|
|
1323
|
+
return proxyStreamRequest(method, path, body, headers);
|
|
1324
|
+
}
|
|
1325
|
+
if (!baseURL) {
|
|
1326
|
+
throw new Error("HTTP client is not initialized. Call initializeApi(...) first.");
|
|
1327
|
+
}
|
|
1328
|
+
const url = `${baseURL}${path}`;
|
|
1329
|
+
logDebug('[smartlinks] stream fetch', { url, method, headers: redactHeaders(headers), body: safeBodyPreview(body) });
|
|
1330
|
+
const response = await fetch(url, {
|
|
1331
|
+
method,
|
|
1332
|
+
headers,
|
|
1333
|
+
body: body === undefined ? undefined : (body instanceof FormData ? body : JSON.stringify(body)),
|
|
1334
|
+
});
|
|
1335
|
+
logDebug('[smartlinks] stream response', { url, status: response.status, ok: response.ok });
|
|
1336
|
+
if (!response.ok) {
|
|
1337
|
+
throw await createFetchError(response, url);
|
|
1338
|
+
}
|
|
1339
|
+
if (!response.body) {
|
|
1340
|
+
throw new Error('Streaming response body is unavailable in this environment');
|
|
1341
|
+
}
|
|
1342
|
+
return parseSseStream(response.body);
|
|
1343
|
+
}
|
|
1108
1344
|
/**
|
|
1109
1345
|
* Internal helper that performs a DELETE request to `${baseURL}${path}`,
|
|
1110
1346
|
* injecting headers for apiKey or bearerToken if present.
|
|
@@ -31,6 +31,7 @@ export declare class IframeResponder {
|
|
|
31
31
|
private options;
|
|
32
32
|
private cache;
|
|
33
33
|
private uploads;
|
|
34
|
+
private activeStreams;
|
|
34
35
|
private isInitialLoad;
|
|
35
36
|
private messageHandler;
|
|
36
37
|
private resizeHandler;
|
|
@@ -59,6 +60,9 @@ export declare class IframeResponder {
|
|
|
59
60
|
private handleRouteChange;
|
|
60
61
|
private handleStandardMessage;
|
|
61
62
|
private handleProxyRequest;
|
|
63
|
+
private handleProxyStreamAbort;
|
|
64
|
+
private handleProxyStreamRequest;
|
|
65
|
+
private forwardProxyStream;
|
|
62
66
|
private getCachedResponse;
|
|
63
67
|
private handleUpload;
|
|
64
68
|
private sendResponse;
|