@fluid-app/droplet-bridge 0.0.0-stage → 0.1.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/README.md +121 -2
- package/dist/index.cjs +1248 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +243 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +243 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1214 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +43 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1248 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
var FluidBridgeError = class extends Error {
|
|
4
|
+
code;
|
|
5
|
+
details;
|
|
6
|
+
constructor(code, message, details) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "FluidBridgeError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.details = details;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var FluidBridgeConfigurationError = class extends FluidBridgeError {
|
|
14
|
+
constructor(message, details) {
|
|
15
|
+
super("configuration_error", message, details);
|
|
16
|
+
this.name = "ConfigurationError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var FluidBridgeHostUnavailableError = class extends FluidBridgeError {
|
|
20
|
+
constructor(message = "Fluid App Bridge host is unavailable", details) {
|
|
21
|
+
super("host_unavailable", message, details);
|
|
22
|
+
this.name = "HostUnavailableError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var FluidBridgeHandshakeTimeoutError = class extends FluidBridgeError {
|
|
26
|
+
constructor(message = "Fluid App Bridge host did not respond before the handshake timed out", details) {
|
|
27
|
+
super("handshake_timeout", message, details);
|
|
28
|
+
this.name = "HandshakeTimeoutError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var FluidBridgeIncompatibleVersionError = class extends FluidBridgeError {
|
|
32
|
+
constructor(message = "Fluid App Bridge SDK is not compatible with the host", details) {
|
|
33
|
+
super("incompatible_version", message, details);
|
|
34
|
+
this.name = "IncompatibleVersionError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var FluidBridgeTokenMintError = class extends FluidBridgeError {
|
|
38
|
+
constructor(message = "Fluid App Bridge host could not mint a session token", details) {
|
|
39
|
+
super("token_mint_failed", message, details);
|
|
40
|
+
this.name = "TokenMintFailedError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var FluidBridgeUnauthorizedError = class extends FluidBridgeError {
|
|
44
|
+
constructor(message = "Fluid App Bridge request is unauthorized", details) {
|
|
45
|
+
super("unauthorized", message, details);
|
|
46
|
+
this.name = "UnauthorizedError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var FluidBridgeForbiddenOriginError = class extends FluidBridgeError {
|
|
50
|
+
constructor(message = "Fluid App Bridge host origin is forbidden", details) {
|
|
51
|
+
super("forbidden_origin", message, details);
|
|
52
|
+
this.name = "ForbiddenOriginError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var FluidBridgeInvalidContextError = class extends FluidBridgeError {
|
|
56
|
+
constructor(message = "Fluid App Bridge context is invalid", details) {
|
|
57
|
+
super("invalid_context", message, details);
|
|
58
|
+
this.name = "InvalidContextError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var FluidBridgeRequestTimeoutError = class extends FluidBridgeError {
|
|
62
|
+
constructor(message = "Fluid App Bridge request timed out", details) {
|
|
63
|
+
super("request_timeout", message, details);
|
|
64
|
+
this.name = "RequestTimeoutError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var FluidBridgeFetchTargetError = class extends FluidBridgeError {
|
|
68
|
+
constructor(message = "Fluid App Bridge fetch target is not allowed", details) {
|
|
69
|
+
super("fetch_target_not_allowed", message, details);
|
|
70
|
+
this.name = "FetchTargetNotAllowedError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var FluidBridgeNetworkError = class extends FluidBridgeError {
|
|
74
|
+
constructor(message = "Fluid App Bridge fetch failed", details) {
|
|
75
|
+
super("network_error", message, details);
|
|
76
|
+
this.name = "NetworkError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var FluidBridgeFailedAuthenticationError = class extends FluidBridgeError {
|
|
80
|
+
constructor(message = "Fluid App Bridge authentication failed", details) {
|
|
81
|
+
super("failed_authentication", message, details);
|
|
82
|
+
this.name = "FailedAuthenticationError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/protocol.ts
|
|
87
|
+
const FLUID_BRIDGE_PROTOCOL_VERSION = "2026-07";
|
|
88
|
+
const APP_BRIDGE_SDK_NAME = "@fluid-app/droplet-bridge";
|
|
89
|
+
const APP_BRIDGE_SDK_VERSION = "0.1.0";
|
|
90
|
+
const FluidBridgeMessageType = {
|
|
91
|
+
SessionTokenRequest: "FLUID::SESSION_TOKEN_REQUEST",
|
|
92
|
+
SessionTokenRespond: "FLUID::SESSION_TOKEN_RESPOND",
|
|
93
|
+
ContextRequest: "FLUID::CONTEXT_REQUEST",
|
|
94
|
+
ContextRespond: "FLUID::CONTEXT_RESPOND",
|
|
95
|
+
FailedAuthentication: "FLUID::ERROR::FAILED_AUTHENTICATION",
|
|
96
|
+
Unauthorized: "FLUID::ERROR::UNAUTHORIZED",
|
|
97
|
+
IncompatibleVersion: "FLUID::ERROR::INCOMPATIBLE_VERSION",
|
|
98
|
+
TokenMintFailed: "FLUID::ERROR::TOKEN_MINT_FAILED",
|
|
99
|
+
InvalidContext: "FLUID::ERROR::INVALID_CONTEXT"
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/host.ts
|
|
103
|
+
function createFluidBridgeHost(options) {
|
|
104
|
+
const targetWindow = options.targetWindow ?? window;
|
|
105
|
+
const appOrigin = normalizeOrigin(options.appOrigin);
|
|
106
|
+
const handleMessage = (event) => {
|
|
107
|
+
if (event.origin !== appOrigin) return;
|
|
108
|
+
if (event.source !== options.iframe.contentWindow) return;
|
|
109
|
+
if (!isBridgeEnvelope(event.data)) return;
|
|
110
|
+
respondToBridgeMessage(options, event.data, appOrigin);
|
|
111
|
+
};
|
|
112
|
+
targetWindow.addEventListener("message", handleMessage);
|
|
113
|
+
return { destroy: () => targetWindow.removeEventListener("message", handleMessage) };
|
|
114
|
+
}
|
|
115
|
+
async function respondToBridgeMessage(options, envelope, appOrigin) {
|
|
116
|
+
const childWindow = options.iframe.contentWindow;
|
|
117
|
+
if (!childWindow) return;
|
|
118
|
+
try {
|
|
119
|
+
validateRequestPayload(envelope.payload, options.clientId);
|
|
120
|
+
switch (envelope.type) {
|
|
121
|
+
case FluidBridgeMessageType.ContextRequest: {
|
|
122
|
+
const context = await options.getContext();
|
|
123
|
+
childWindow.postMessage({
|
|
124
|
+
id: envelope.id,
|
|
125
|
+
type: FluidBridgeMessageType.ContextRespond,
|
|
126
|
+
payload: context
|
|
127
|
+
}, appOrigin);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
case FluidBridgeMessageType.SessionTokenRequest: {
|
|
131
|
+
const context = await options.getContext();
|
|
132
|
+
const sessionToken = await options.getSessionToken(context);
|
|
133
|
+
if (sessionToken.length === 0) throw new FluidBridgeTokenMintError("Fluid App Bridge host returned an empty session token");
|
|
134
|
+
childWindow.postMessage({
|
|
135
|
+
id: envelope.id,
|
|
136
|
+
type: FluidBridgeMessageType.SessionTokenRespond,
|
|
137
|
+
payload: { sessionToken }
|
|
138
|
+
}, appOrigin);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
default: throw new FluidBridgeForbiddenOriginError("Fluid App Bridge host received an unsupported message type", { type: envelope.type });
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
childWindow.postMessage({
|
|
145
|
+
id: envelope.id,
|
|
146
|
+
type: errorTypeFor(error),
|
|
147
|
+
payload: errorPayloadFor(error)
|
|
148
|
+
}, appOrigin);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function validateRequestPayload(value, clientId) {
|
|
152
|
+
if (typeof value !== "object" || value === null) throw new FluidBridgeInvalidContextError("Fluid App Bridge request payload is invalid");
|
|
153
|
+
if (!("clientId" in value) || value.clientId !== clientId) throw new FluidBridgeFailedAuthenticationError("Fluid App Bridge request client ID does not match the host context");
|
|
154
|
+
if (!("protocol" in value) || value.protocol !== "2026-07") throw new FluidBridgeIncompatibleVersionError();
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
function isBridgeEnvelope(value) {
|
|
158
|
+
if (typeof value !== "object" || value === null) return false;
|
|
159
|
+
return "id" in value && "type" in value && "payload" in value && typeof value.id === "string" && typeof value.type === "string";
|
|
160
|
+
}
|
|
161
|
+
function errorTypeFor(error) {
|
|
162
|
+
if (error instanceof FluidBridgeFailedAuthenticationError) return FluidBridgeMessageType.FailedAuthentication;
|
|
163
|
+
if (error instanceof FluidBridgeIncompatibleVersionError) return FluidBridgeMessageType.IncompatibleVersion;
|
|
164
|
+
if (error instanceof FluidBridgeUnauthorizedError) return FluidBridgeMessageType.Unauthorized;
|
|
165
|
+
if (error instanceof FluidBridgeTokenMintError) return FluidBridgeMessageType.TokenMintFailed;
|
|
166
|
+
if (error instanceof FluidBridgeInvalidContextError) return FluidBridgeMessageType.InvalidContext;
|
|
167
|
+
if (error instanceof FluidBridgeForbiddenOriginError) return FluidBridgeMessageType.InvalidContext;
|
|
168
|
+
return FluidBridgeMessageType.TokenMintFailed;
|
|
169
|
+
}
|
|
170
|
+
function errorPayloadFor(error) {
|
|
171
|
+
if (error instanceof Error && "code" in error && typeof error.code === "string") return {
|
|
172
|
+
code: error.code,
|
|
173
|
+
message: error.message,
|
|
174
|
+
details: "details" in error ? error.details : void 0
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
code: "token_mint_failed",
|
|
178
|
+
message: "Fluid App Bridge host could not complete the request",
|
|
179
|
+
details: error
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function normalizeOrigin(origin) {
|
|
183
|
+
return new URL(origin).origin;
|
|
184
|
+
}
|
|
185
|
+
const unknownShape = { kind: "unknown" };
|
|
186
|
+
const stringShape = { kind: "string" };
|
|
187
|
+
const nonEmptyStringShape = {
|
|
188
|
+
kind: "string",
|
|
189
|
+
nonEmpty: true
|
|
190
|
+
};
|
|
191
|
+
const emptyObjectShape = { kind: "object" };
|
|
192
|
+
const unspecifiedObjectShape = {
|
|
193
|
+
kind: "object",
|
|
194
|
+
additionalProperties: true
|
|
195
|
+
};
|
|
196
|
+
const stringArrayShape = { kind: "string-array" };
|
|
197
|
+
const bridgeContextShape = {
|
|
198
|
+
kind: "object",
|
|
199
|
+
required: {
|
|
200
|
+
protocolVersion: {
|
|
201
|
+
kind: "string-enum",
|
|
202
|
+
values: ["1"]
|
|
203
|
+
},
|
|
204
|
+
app: {
|
|
205
|
+
kind: "object",
|
|
206
|
+
required: { clientId: nonEmptyStringShape }
|
|
207
|
+
},
|
|
208
|
+
store: {
|
|
209
|
+
kind: "object",
|
|
210
|
+
required: {
|
|
211
|
+
subdomain: nonEmptyStringShape,
|
|
212
|
+
url: nonEmptyStringShape
|
|
213
|
+
},
|
|
214
|
+
optional: {
|
|
215
|
+
uuid: stringShape,
|
|
216
|
+
name: stringShape,
|
|
217
|
+
locale: nonEmptyStringShape
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
actor: {
|
|
221
|
+
kind: "object",
|
|
222
|
+
required: { type: {
|
|
223
|
+
kind: "string-enum",
|
|
224
|
+
values: ["store_admin", "store_member"]
|
|
225
|
+
} },
|
|
226
|
+
optional: {
|
|
227
|
+
uuid: stringShape,
|
|
228
|
+
roles: stringArrayShape,
|
|
229
|
+
permissions: stringArrayShape,
|
|
230
|
+
memberType: stringShape
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
entry: {
|
|
234
|
+
kind: "object",
|
|
235
|
+
required: { type: {
|
|
236
|
+
kind: "string-enum",
|
|
237
|
+
values: [
|
|
238
|
+
"droplet",
|
|
239
|
+
"drop_zone",
|
|
240
|
+
"mobile_embed",
|
|
241
|
+
"portal_embed",
|
|
242
|
+
"app_extension"
|
|
243
|
+
]
|
|
244
|
+
} },
|
|
245
|
+
optional: { name: stringShape }
|
|
246
|
+
},
|
|
247
|
+
resources: {
|
|
248
|
+
kind: "array",
|
|
249
|
+
items: {
|
|
250
|
+
kind: "object",
|
|
251
|
+
required: {
|
|
252
|
+
type: nonEmptyStringShape,
|
|
253
|
+
id: nonEmptyStringShape
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
fetch: {
|
|
258
|
+
kind: "object",
|
|
259
|
+
required: { allowedOrigins: { kind: "absolute-origin-array" } }
|
|
260
|
+
},
|
|
261
|
+
sdk: {
|
|
262
|
+
kind: "object",
|
|
263
|
+
required: { minVersion: nonEmptyStringShape },
|
|
264
|
+
optional: { recommendedVersion: stringShape }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const BRIDGE_MESSAGE_CATALOG = {
|
|
269
|
+
"bridge.ready": {
|
|
270
|
+
direction: "iframe-to-shell",
|
|
271
|
+
expectsResponse: false,
|
|
272
|
+
payloadShape: emptyObjectShape
|
|
273
|
+
},
|
|
274
|
+
"session_token.deliver": {
|
|
275
|
+
direction: "shell-to-iframe",
|
|
276
|
+
expectsResponse: false,
|
|
277
|
+
payloadShape: {
|
|
278
|
+
kind: "object",
|
|
279
|
+
required: { sessionToken: nonEmptyStringShape }
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"session_token.refresh_request": {
|
|
283
|
+
direction: "iframe-to-shell",
|
|
284
|
+
expectsResponse: {
|
|
285
|
+
messageTypes: ["session_token.deliver"],
|
|
286
|
+
correlation: "request_id"
|
|
287
|
+
},
|
|
288
|
+
payloadShape: {
|
|
289
|
+
kind: "object",
|
|
290
|
+
required: {
|
|
291
|
+
clientId: nonEmptyStringShape,
|
|
292
|
+
sdkVersion: nonEmptyStringShape,
|
|
293
|
+
protocolVersion: nonEmptyStringShape
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
"context.deliver": {
|
|
298
|
+
direction: "shell-to-iframe",
|
|
299
|
+
expectsResponse: false,
|
|
300
|
+
payloadShape: bridgeContextShape
|
|
301
|
+
},
|
|
302
|
+
"nav.request": {
|
|
303
|
+
direction: "iframe-to-shell",
|
|
304
|
+
expectsResponse: {
|
|
305
|
+
messageTypes: ["nav.ack", "nav.reject"],
|
|
306
|
+
correlation: "request_id"
|
|
307
|
+
},
|
|
308
|
+
payloadShape: {
|
|
309
|
+
kind: "object",
|
|
310
|
+
required: {
|
|
311
|
+
target: nonEmptyStringShape,
|
|
312
|
+
params: {
|
|
313
|
+
kind: "record",
|
|
314
|
+
values: unknownShape
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
"nav.ack": {
|
|
320
|
+
direction: "shell-to-iframe",
|
|
321
|
+
expectsResponse: false,
|
|
322
|
+
payloadShape: emptyObjectShape
|
|
323
|
+
},
|
|
324
|
+
"nav.reject": {
|
|
325
|
+
direction: "shell-to-iframe",
|
|
326
|
+
expectsResponse: false,
|
|
327
|
+
payloadShape: {
|
|
328
|
+
kind: "object",
|
|
329
|
+
required: { reason: nonEmptyStringShape }
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
"modal.open": {
|
|
333
|
+
direction: "iframe-to-shell",
|
|
334
|
+
expectsResponse: {
|
|
335
|
+
messageTypes: ["modal.close"],
|
|
336
|
+
correlation: "lifecycle"
|
|
337
|
+
},
|
|
338
|
+
payloadShape: unspecifiedObjectShape
|
|
339
|
+
},
|
|
340
|
+
"modal.close": {
|
|
341
|
+
direction: "shell-to-iframe",
|
|
342
|
+
expectsResponse: false,
|
|
343
|
+
payloadShape: emptyObjectShape
|
|
344
|
+
},
|
|
345
|
+
"toast.show": {
|
|
346
|
+
direction: "iframe-to-shell",
|
|
347
|
+
expectsResponse: false,
|
|
348
|
+
payloadShape: unspecifiedObjectShape
|
|
349
|
+
},
|
|
350
|
+
"redirect.request": {
|
|
351
|
+
direction: "iframe-to-shell",
|
|
352
|
+
expectsResponse: false,
|
|
353
|
+
payloadShape: unspecifiedObjectShape
|
|
354
|
+
},
|
|
355
|
+
error: {
|
|
356
|
+
direction: "both",
|
|
357
|
+
expectsResponse: false,
|
|
358
|
+
payloadShape: {
|
|
359
|
+
kind: "object",
|
|
360
|
+
required: {
|
|
361
|
+
code: nonEmptyStringShape,
|
|
362
|
+
message: nonEmptyStringShape
|
|
363
|
+
},
|
|
364
|
+
optional: { details: {
|
|
365
|
+
kind: "record",
|
|
366
|
+
values: unknownShape
|
|
367
|
+
} }
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
function parseBridgeEnvelope(value, options) {
|
|
372
|
+
try {
|
|
373
|
+
if (!isRecord(value)) return {
|
|
374
|
+
ok: false,
|
|
375
|
+
reason: "malformed"
|
|
376
|
+
};
|
|
377
|
+
const requestId = typeof value.request_id === "string" && uuidV7Pattern.test(value.request_id) ? value.request_id : void 0;
|
|
378
|
+
if (!hasExactKeys(value, envelopeKeys)) return parseFailure("malformed", requestId);
|
|
379
|
+
if (value.version !== "1") return parseFailure("version_mismatch", requestId);
|
|
380
|
+
if (typeof value.type !== "string" || typeof value.request_id !== "string" || !uuidV7Pattern.test(value.request_id)) return parseFailure("malformed", requestId);
|
|
381
|
+
if (!isBridgeMessageType(value.type)) return parseFailure("unknown_type", requestId);
|
|
382
|
+
const catalogEntry = BRIDGE_MESSAGE_CATALOG[value.type];
|
|
383
|
+
if (!isDirectionAllowed(catalogEntry.direction, options.receivedBy)) return parseFailure("wrong_direction", requestId);
|
|
384
|
+
if (!matchesPayloadShape(value.payload, catalogEntry.payloadShape)) return parseFailure("invalid_payload", requestId);
|
|
385
|
+
return {
|
|
386
|
+
ok: true,
|
|
387
|
+
envelope: value
|
|
388
|
+
};
|
|
389
|
+
} catch {
|
|
390
|
+
return {
|
|
391
|
+
ok: false,
|
|
392
|
+
reason: "malformed"
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const envelopeKeys = [
|
|
397
|
+
"type",
|
|
398
|
+
"payload",
|
|
399
|
+
"request_id",
|
|
400
|
+
"version"
|
|
401
|
+
];
|
|
402
|
+
const uuidV7Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
403
|
+
function isRecord(value) {
|
|
404
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
405
|
+
const prototype = Object.getPrototypeOf(value);
|
|
406
|
+
return prototype === Object.prototype || prototype === null;
|
|
407
|
+
}
|
|
408
|
+
function isDenseArray(value) {
|
|
409
|
+
if (!Array.isArray(value)) return false;
|
|
410
|
+
if (Object.keys(value).length !== value.length) return false;
|
|
411
|
+
for (let index = 0; index < value.length; index += 1) if (!Object.hasOwn(value, index)) return false;
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
function hasExactKeys(value, expectedKeys) {
|
|
415
|
+
return Object.keys(value).length === expectedKeys.length && expectedKeys.every((key) => Object.hasOwn(value, key));
|
|
416
|
+
}
|
|
417
|
+
function isBridgeMessageType(value) {
|
|
418
|
+
return Object.hasOwn(BRIDGE_MESSAGE_CATALOG, value);
|
|
419
|
+
}
|
|
420
|
+
function parseFailure(reason, requestId) {
|
|
421
|
+
return requestId === void 0 ? {
|
|
422
|
+
ok: false,
|
|
423
|
+
reason
|
|
424
|
+
} : {
|
|
425
|
+
ok: false,
|
|
426
|
+
reason,
|
|
427
|
+
requestId
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function isDirectionAllowed(direction, receivedBy) {
|
|
431
|
+
return direction === "both" || direction === "iframe-to-shell" && receivedBy === "shell" || direction === "shell-to-iframe" && receivedBy === "iframe";
|
|
432
|
+
}
|
|
433
|
+
function matchesPayloadShape(value, shape) {
|
|
434
|
+
if (shape.kind === "unknown") return true;
|
|
435
|
+
if (shape.kind === "string") return typeof value === "string" && (shape.nonEmpty !== true || value.length > 0);
|
|
436
|
+
if (shape.kind === "string-array") return isDenseArray(value) && value.every((item) => typeof item === "string");
|
|
437
|
+
if (shape.kind === "absolute-origin-array") return isDenseArray(value) && value.every(isAbsoluteOrigin);
|
|
438
|
+
if (shape.kind === "string-enum") return typeof value === "string" && shape.values.includes(value);
|
|
439
|
+
if (shape.kind === "array") return isDenseArray(value) && value.every((item) => matchesPayloadShape(item, shape.items));
|
|
440
|
+
if (shape.kind === "record") return isRecord(value) && Object.values(value).every((item) => matchesPayloadShape(item, shape.values));
|
|
441
|
+
if (!isRecord(value)) return false;
|
|
442
|
+
const required = shape.required ?? {};
|
|
443
|
+
const optional = shape.optional ?? {};
|
|
444
|
+
const knownKeys = new Set([...Object.keys(required), ...Object.keys(optional)]);
|
|
445
|
+
if (!Object.entries(required).every(([key, fieldShape]) => Object.hasOwn(value, key) && matchesPayloadShape(value[key], fieldShape))) return false;
|
|
446
|
+
if (!Object.entries(optional).every(([key, fieldShape]) => !Object.hasOwn(value, key) || matchesPayloadShape(value[key], fieldShape))) return false;
|
|
447
|
+
return shape.additionalProperties === true || Object.keys(value).every((key) => knownKeys.has(key));
|
|
448
|
+
}
|
|
449
|
+
function isAbsoluteOrigin(value) {
|
|
450
|
+
if (typeof value !== "string" || value === "*") return false;
|
|
451
|
+
try {
|
|
452
|
+
const url = new URL(value);
|
|
453
|
+
return (url.protocol === "https:" || url.protocol === "http:") && url.origin !== "null" && url.origin === value;
|
|
454
|
+
} catch {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region src/canonical-host.ts
|
|
460
|
+
const REQUEST_TIMEOUT_MS = 5e3;
|
|
461
|
+
const MAX_PENDING_REQUESTS = 16;
|
|
462
|
+
const CONTEXT_VALIDATION_ID = "00000000-0000-7000-8000-000000000000";
|
|
463
|
+
/** Push handshake and correlated refresh on the single canonical envelope. */
|
|
464
|
+
function createCanonicalBridgeHost(options) {
|
|
465
|
+
const targetWindow = options.targetWindow ?? window;
|
|
466
|
+
const appOrigin = exactOrigin$1(options.appOrigin);
|
|
467
|
+
const mount = Object.freeze({
|
|
468
|
+
...options.mount,
|
|
469
|
+
actor: Object.freeze({ ...options.mount.actor }),
|
|
470
|
+
entry: Object.freeze({ ...options.mount.entry })
|
|
471
|
+
});
|
|
472
|
+
if (!mount.clientId.trim() || !mount.storeUuid.trim() || !mount.actor.uuid.trim() || !mount.installationId.trim() || !mount.entry.name.trim() || !mount.sid.trim()) throw new FluidBridgeConfigurationError("Host mount identifiers must be present");
|
|
473
|
+
const iframe = options.iframe;
|
|
474
|
+
const frameWindow = iframe.contentWindow;
|
|
475
|
+
if (!frameWindow) throw new FluidBridgeConfigurationError("Host requires a mounted iframe");
|
|
476
|
+
const frameSrc = iframe.src;
|
|
477
|
+
if (new URL(frameSrc).origin !== appOrigin) throw new FluidBridgeConfigurationError("Iframe does not match its registered origin");
|
|
478
|
+
let destroyed = false;
|
|
479
|
+
let context;
|
|
480
|
+
let originBaseline;
|
|
481
|
+
let ready = false;
|
|
482
|
+
const pendingReady = /* @__PURE__ */ new Map();
|
|
483
|
+
const pendingRefresh = /* @__PURE__ */ new Map();
|
|
484
|
+
const isCurrent = () => !destroyed && iframe.contentWindow === frameWindow && iframe.src === frameSrc;
|
|
485
|
+
const post = (envelope) => {
|
|
486
|
+
if (isCurrent()) frameWindow.postMessage(envelope, appOrigin);
|
|
487
|
+
};
|
|
488
|
+
const fail = (requestId, payload) => {
|
|
489
|
+
post({
|
|
490
|
+
type: "error",
|
|
491
|
+
payload,
|
|
492
|
+
request_id: requestId,
|
|
493
|
+
version: "1"
|
|
494
|
+
});
|
|
495
|
+
};
|
|
496
|
+
const run = async (requestId, operation, initial) => {
|
|
497
|
+
const pool = initial ? pendingReady : pendingRefresh;
|
|
498
|
+
if (pool.has(requestId)) return;
|
|
499
|
+
if (pool.size >= MAX_PENDING_REQUESTS) {
|
|
500
|
+
fail(requestId, {
|
|
501
|
+
code: "host_unavailable",
|
|
502
|
+
message: "Host request capacity exceeded"
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const controller = new AbortController();
|
|
507
|
+
pool.set(requestId, controller);
|
|
508
|
+
const timer = setTimeout(() => controller.abort(new FluidBridgeRequestTimeoutError()), REQUEST_TIMEOUT_MS);
|
|
509
|
+
try {
|
|
510
|
+
await abortable$1(operation(controller.signal), controller.signal);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (isCurrent()) fail(requestId, safeError(error, initial));
|
|
513
|
+
} finally {
|
|
514
|
+
clearTimeout(timer);
|
|
515
|
+
pool.delete(requestId);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
const mint = async (signal) => {
|
|
519
|
+
const token = await options.getSessionToken(mount, signal);
|
|
520
|
+
if (signal.aborted) throw signal.reason;
|
|
521
|
+
if (typeof token !== "string" || !tokenMatchesMount(token, mount)) throw new FluidBridgeTokenMintError();
|
|
522
|
+
return token;
|
|
523
|
+
};
|
|
524
|
+
const fetchContext = async (signal) => {
|
|
525
|
+
let candidate;
|
|
526
|
+
try {
|
|
527
|
+
candidate = await options.getContext(signal);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (signal.aborted || error instanceof FluidBridgeError) throw error;
|
|
530
|
+
throw new FluidBridgeInvalidContextError();
|
|
531
|
+
}
|
|
532
|
+
if (signal.aborted) throw signal.reason;
|
|
533
|
+
return boundContext(candidate, mount, appOrigin);
|
|
534
|
+
};
|
|
535
|
+
const deliver = async (requestId, signal) => {
|
|
536
|
+
const deliveredContext = await fetchContext(signal);
|
|
537
|
+
const token = await mint(signal);
|
|
538
|
+
if (signal.aborted) throw signal.reason;
|
|
539
|
+
if (!isCurrent()) return;
|
|
540
|
+
context = deliveredContext;
|
|
541
|
+
originBaseline = originBaseline ? new Set([...originBaseline, ...deliveredContext.fetch.allowedOrigins]) : new Set(deliveredContext.fetch.allowedOrigins);
|
|
542
|
+
post({
|
|
543
|
+
type: "context.deliver",
|
|
544
|
+
payload: deliveredContext,
|
|
545
|
+
request_id: requestId,
|
|
546
|
+
version: "1"
|
|
547
|
+
});
|
|
548
|
+
post({
|
|
549
|
+
type: "session_token.deliver",
|
|
550
|
+
payload: { sessionToken: token },
|
|
551
|
+
request_id: requestId,
|
|
552
|
+
version: "1"
|
|
553
|
+
});
|
|
554
|
+
ready = true;
|
|
555
|
+
};
|
|
556
|
+
const refresh = async (requestId, payload, signal) => {
|
|
557
|
+
if (!ready || !context) {
|
|
558
|
+
fail(requestId, {
|
|
559
|
+
code: "host_unavailable",
|
|
560
|
+
message: "Complete the handshake before refreshing"
|
|
561
|
+
});
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (payload.clientId !== mount.clientId) {
|
|
565
|
+
fail(requestId, {
|
|
566
|
+
code: "failed_authentication",
|
|
567
|
+
message: "Request does not match the mounted app"
|
|
568
|
+
});
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (payload.protocolVersion !== "1" || !supportsSdk$1(payload.sdkVersion, context.sdk.minVersion)) {
|
|
572
|
+
fail(requestId, {
|
|
573
|
+
code: "incompatible_version",
|
|
574
|
+
message: "SDK or protocol version is incompatible",
|
|
575
|
+
details: {
|
|
576
|
+
received: {
|
|
577
|
+
sdkVersion: payload.sdkVersion.slice(0, 32),
|
|
578
|
+
protocolVersion: payload.protocolVersion.slice(0, 32)
|
|
579
|
+
},
|
|
580
|
+
required: {
|
|
581
|
+
sdkVersion: context.sdk.minVersion,
|
|
582
|
+
protocolVersion: "1"
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const authorizedContext = await fetchContext(signal);
|
|
589
|
+
if (!supportsSdk$1(payload.sdkVersion, authorizedContext.sdk.minVersion)) {
|
|
590
|
+
fail(requestId, {
|
|
591
|
+
code: "incompatible_version",
|
|
592
|
+
message: "SDK version is incompatible with the current context"
|
|
593
|
+
});
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (originBaseline && [...originBaseline].some((origin) => !authorizedContext.fetch.allowedOrigins.includes(origin))) {
|
|
597
|
+
fail(requestId, {
|
|
598
|
+
code: "unauthorized",
|
|
599
|
+
message: "Authorized fetch origins changed for the current document"
|
|
600
|
+
});
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
context = authorizedContext;
|
|
604
|
+
const token = await mint(signal);
|
|
605
|
+
if (signal.aborted) throw signal.reason;
|
|
606
|
+
post({
|
|
607
|
+
type: "session_token.deliver",
|
|
608
|
+
payload: { sessionToken: token },
|
|
609
|
+
request_id: requestId,
|
|
610
|
+
version: "1"
|
|
611
|
+
});
|
|
612
|
+
};
|
|
613
|
+
const handleMessage = (event) => {
|
|
614
|
+
if (!isCurrent() || event.origin !== appOrigin || event.source !== frameWindow) return;
|
|
615
|
+
const parsed = parseBridgeEnvelope(event.data, { receivedBy: "shell" });
|
|
616
|
+
if (!parsed.ok) {
|
|
617
|
+
if (parsed.reason === "version_mismatch" && parsed.requestId) fail(parsed.requestId, {
|
|
618
|
+
code: "version_mismatch",
|
|
619
|
+
message: "Unsupported bridge protocol version"
|
|
620
|
+
});
|
|
621
|
+
else if (parsed.reason === "invalid_payload" && parsed.requestId && typeof event.data === "object" && event.data !== null && "type" in event.data && event.data.type === "session_token.refresh_request") fail(parsed.requestId, {
|
|
622
|
+
code: "incompatible_version",
|
|
623
|
+
message: "Refresh requires client, SDK and protocol metadata"
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const envelope = parsed.envelope;
|
|
628
|
+
if (envelope.type === "bridge.ready") {
|
|
629
|
+
if (pendingReady.has(envelope.request_id)) return;
|
|
630
|
+
run(envelope.request_id, (signal) => deliver(envelope.request_id, signal), true);
|
|
631
|
+
} else if (envelope.type === "session_token.refresh_request") run(envelope.request_id, (signal) => refresh(envelope.request_id, envelope.payload, signal), false);
|
|
632
|
+
};
|
|
633
|
+
targetWindow.addEventListener("message", handleMessage);
|
|
634
|
+
return { destroy: () => {
|
|
635
|
+
destroyed = true;
|
|
636
|
+
targetWindow.removeEventListener("message", handleMessage);
|
|
637
|
+
for (const controller of pendingReady.values()) controller.abort();
|
|
638
|
+
for (const controller of pendingRefresh.values()) controller.abort();
|
|
639
|
+
pendingReady.clear();
|
|
640
|
+
pendingRefresh.clear();
|
|
641
|
+
context = void 0;
|
|
642
|
+
originBaseline = void 0;
|
|
643
|
+
ready = false;
|
|
644
|
+
} };
|
|
645
|
+
}
|
|
646
|
+
function tokenMatchesMount(token, mount) {
|
|
647
|
+
try {
|
|
648
|
+
if (!/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) return false;
|
|
649
|
+
const encoded = token.split(".")[1];
|
|
650
|
+
if (!encoded) return false;
|
|
651
|
+
const claims = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(atob(encoded.replace(/-/g, "+").replace(/_/g, "/")), (character) => character.charCodeAt(0))));
|
|
652
|
+
if (typeof claims !== "object" || claims === null || Array.isArray(claims)) return false;
|
|
653
|
+
const payload = claims;
|
|
654
|
+
const entry = payload.entry;
|
|
655
|
+
return payload.aud === mount.clientId && payload.dest === `urn:fluid:store:${mount.storeUuid}` && payload.sub === `urn:fluid:${mount.actor.type}:${mount.actor.uuid}` && payload.sid === mount.sid && typeof payload.exp === "number" && Number.isInteger(payload.exp) && typeof entry === "object" && entry !== null && "type" in entry && entry.type === mount.entry.type && "name" in entry && entry.name === mount.entry.name;
|
|
656
|
+
} catch {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function exactOrigin$1(value) {
|
|
661
|
+
try {
|
|
662
|
+
const url = new URL(value);
|
|
663
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && url.origin === value) return value;
|
|
664
|
+
} catch {}
|
|
665
|
+
throw new FluidBridgeConfigurationError("Host requires an exact HTTP(S) app origin");
|
|
666
|
+
}
|
|
667
|
+
function boundContext(value, mount, appOrigin) {
|
|
668
|
+
const parsed = parseBridgeEnvelope({
|
|
669
|
+
type: "context.deliver",
|
|
670
|
+
payload: value,
|
|
671
|
+
request_id: CONTEXT_VALIDATION_ID,
|
|
672
|
+
version: "1"
|
|
673
|
+
}, { receivedBy: "iframe" });
|
|
674
|
+
if (!parsed.ok || parsed.envelope.type !== "context.deliver") throw new FluidBridgeInvalidContextError();
|
|
675
|
+
const context = parsed.envelope.payload;
|
|
676
|
+
if (context.app.clientId !== mount.clientId || context.store.uuid !== mount.storeUuid || context.actor.type !== mount.actor.type || context.actor.uuid !== mount.actor.uuid || context.entry.type !== mount.entry.type || context.entry.name !== mount.entry.name || !context.fetch.allowedOrigins.includes(appOrigin) || !supportsSdk$1(context.sdk.minVersion, context.sdk.minVersion)) throw new FluidBridgeInvalidContextError();
|
|
677
|
+
return context;
|
|
678
|
+
}
|
|
679
|
+
function supportsSdk$1(received, minimum) {
|
|
680
|
+
const pattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
681
|
+
if (!pattern.test(received) || !pattern.test(minimum)) return false;
|
|
682
|
+
const current = received.split(".").map(Number);
|
|
683
|
+
const required = minimum.split(".").map(Number);
|
|
684
|
+
if (![...current, ...required].every(Number.isSafeInteger)) return false;
|
|
685
|
+
for (const index of [
|
|
686
|
+
0,
|
|
687
|
+
1,
|
|
688
|
+
2
|
|
689
|
+
]) {
|
|
690
|
+
const left = current[index];
|
|
691
|
+
const right = required[index];
|
|
692
|
+
if (left === void 0 || right === void 0) return false;
|
|
693
|
+
if (left !== right) return left > right;
|
|
694
|
+
}
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
function safeError(error, initial) {
|
|
698
|
+
if (error instanceof FluidBridgeRequestTimeoutError) return {
|
|
699
|
+
code: initial ? "handshake_timeout" : "request_timeout",
|
|
700
|
+
message: "Host request timed out"
|
|
701
|
+
};
|
|
702
|
+
if (error instanceof FluidBridgeError) {
|
|
703
|
+
const code = error.code;
|
|
704
|
+
if (code === "unauthorized" || code === "failed_authentication" || code === "invalid_context" || code === "incompatible_version" || code === "host_unavailable") return {
|
|
705
|
+
code,
|
|
706
|
+
message: "Host could not complete the bridge request"
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
code: "token_mint_failed",
|
|
711
|
+
message: "Host could not obtain a session token"
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function abortable$1(operation, signal) {
|
|
715
|
+
return new Promise((resolve, reject) => {
|
|
716
|
+
const abort = () => reject(signal.reason);
|
|
717
|
+
if (signal.aborted) {
|
|
718
|
+
abort();
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
722
|
+
operation.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/session-token-api.ts
|
|
727
|
+
const DEFAULT_SESSION_TOKEN_PATH = "api/v2025-06/app_bridge/session_token";
|
|
728
|
+
async function mintAppBridgeSessionToken({ request, apiBaseUrl, authToken, fetch: fetchImplementation = globalThis.fetch?.bind(globalThis) }) {
|
|
729
|
+
if (!fetchImplementation) throw new FluidBridgeTokenMintError("Fluid App Bridge host requires a fetch implementation to mint session tokens");
|
|
730
|
+
const response = await fetchImplementation(resolveSessionTokenUrl(apiBaseUrl), {
|
|
731
|
+
method: "POST",
|
|
732
|
+
headers: sessionTokenHeaders(authToken),
|
|
733
|
+
body: JSON.stringify(sessionTokenRequestBody(request))
|
|
734
|
+
});
|
|
735
|
+
const body = await response.json().catch(() => null);
|
|
736
|
+
if (!response.ok) throw errorForTokenResponse(response.status, body);
|
|
737
|
+
if (!isSessionTokenSuccessResponse(body) || body.session_token.length === 0) throw new FluidBridgeTokenMintError("Fluid App Bridge backend returned an invalid session token response", { body });
|
|
738
|
+
return body.session_token;
|
|
739
|
+
}
|
|
740
|
+
function sessionTokenRequestFromContext(context, sessionId) {
|
|
741
|
+
return {
|
|
742
|
+
clientId: context.app.clientId,
|
|
743
|
+
entry: context.entry,
|
|
744
|
+
resources: context.resources,
|
|
745
|
+
allowedOrigins: context.fetch.allowedOrigins,
|
|
746
|
+
...sessionId ? { sessionId } : {}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function sessionTokenHeaders(authToken) {
|
|
750
|
+
const headers = new Headers({
|
|
751
|
+
"Content-Type": "application/json",
|
|
752
|
+
"X-Fluid-App-Bridge-Protocol": FLUID_BRIDGE_PROTOCOL_VERSION,
|
|
753
|
+
"X-Fluid-App-Bridge-Version": APP_BRIDGE_SDK_VERSION,
|
|
754
|
+
"X-Fluid-App-Bridge-Sdk": APP_BRIDGE_SDK_NAME
|
|
755
|
+
});
|
|
756
|
+
if (authToken) headers.set("Authorization", `Bearer ${authToken}`);
|
|
757
|
+
return headers;
|
|
758
|
+
}
|
|
759
|
+
function sessionTokenRequestBody(request) {
|
|
760
|
+
return {
|
|
761
|
+
client_id: request.clientId,
|
|
762
|
+
entry: request.entry,
|
|
763
|
+
resources: request.resources ?? [],
|
|
764
|
+
allowed_origins: request.allowedOrigins ?? [],
|
|
765
|
+
...request.sessionId ? { session_id: request.sessionId } : {}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function resolveSessionTokenUrl(apiBaseUrl) {
|
|
769
|
+
if (!apiBaseUrl) return `/${DEFAULT_SESSION_TOKEN_PATH}`;
|
|
770
|
+
return new URL(DEFAULT_SESSION_TOKEN_PATH, ensureTrailingSlash(apiBaseUrl)).toString();
|
|
771
|
+
}
|
|
772
|
+
function ensureTrailingSlash(url) {
|
|
773
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
774
|
+
}
|
|
775
|
+
function isSessionTokenSuccessResponse(value) {
|
|
776
|
+
return typeof value === "object" && value !== null && "session_token" in value && typeof value.session_token === "string";
|
|
777
|
+
}
|
|
778
|
+
function errorForTokenResponse(status, body) {
|
|
779
|
+
const errorBody = isLegacyErrorResponse(body) ? body.error : void 0;
|
|
780
|
+
const message = errorBody?.message ?? "Fluid App Bridge backend rejected the session-token request";
|
|
781
|
+
const details = errorBody?.details ?? body;
|
|
782
|
+
if (status === 401) return new FluidBridgeUnauthorizedError(message, details);
|
|
783
|
+
if (status === 403) return new FluidBridgeFailedAuthenticationError(message, details);
|
|
784
|
+
if (status === 422) return new FluidBridgeIncompatibleVersionError(message, details);
|
|
785
|
+
return new FluidBridgeTokenMintError(message, details);
|
|
786
|
+
}
|
|
787
|
+
function isLegacyErrorResponse(value) {
|
|
788
|
+
return typeof value === "object" && value !== null && "error" in value;
|
|
789
|
+
}
|
|
790
|
+
//#endregion
|
|
791
|
+
//#region src/client-fetch.ts
|
|
792
|
+
/** Both explicit and ambient fetch use only server-delivered exact origins. */
|
|
793
|
+
function createAuthenticatedFetch(options) {
|
|
794
|
+
return async (input, init) => {
|
|
795
|
+
const baseUrl = options.getBaseUrl();
|
|
796
|
+
let url;
|
|
797
|
+
try {
|
|
798
|
+
url = new URL(input instanceof Request ? input.url : input.toString(), baseUrl);
|
|
799
|
+
} catch {
|
|
800
|
+
throw new FluidBridgeFetchTargetError();
|
|
801
|
+
}
|
|
802
|
+
if (url.hostname === "api.fluid.app" || url.hostname.endsWith(".api.fluid.app")) {
|
|
803
|
+
if (options.ambient) return options.nativeFetch(input, init);
|
|
804
|
+
throw new FluidBridgeFetchTargetError();
|
|
805
|
+
}
|
|
806
|
+
if (options.ambient) {
|
|
807
|
+
const state = options.getHandshakeState();
|
|
808
|
+
if (state === "failed") return options.nativeFetch(input, init);
|
|
809
|
+
if (state === "destroyed" && url.origin !== documentOrigin(baseUrl)) return options.nativeFetch(input, init);
|
|
810
|
+
const delivered = options.getDeliveredContext();
|
|
811
|
+
if (delivered) {
|
|
812
|
+
if (!delivered.fetch.allowedOrigins.includes(url.origin)) return options.nativeFetch(input, init);
|
|
813
|
+
} else if (url.origin !== documentOrigin(baseUrl)) return options.nativeFetch(input, init);
|
|
814
|
+
}
|
|
815
|
+
const signal = resolveSignal(input, init);
|
|
816
|
+
if (signal?.aborted) throw signal.reason;
|
|
817
|
+
let context;
|
|
818
|
+
try {
|
|
819
|
+
context = signal ? await abortable(options.getContext(), signal) : await options.getContext();
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (options.ambient && options.getHandshakeState() === "failed") return options.nativeFetch(input, init);
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
824
|
+
if (!context.fetch.allowedOrigins.includes(url.origin)) {
|
|
825
|
+
if (options.ambient) return options.nativeFetch(input, init);
|
|
826
|
+
throw new FluidBridgeFetchTargetError();
|
|
827
|
+
}
|
|
828
|
+
const locale = context.store.locale;
|
|
829
|
+
if (!locale || !/^[a-z]{2,3}(?:[-_][a-z0-9]{2,8})*$/i.test(locale)) throw new FluidBridgeInvalidContextError();
|
|
830
|
+
let request;
|
|
831
|
+
try {
|
|
832
|
+
request = new Request(input instanceof Request ? input : url, init);
|
|
833
|
+
} catch {
|
|
834
|
+
throw new FluidBridgeNetworkError();
|
|
835
|
+
}
|
|
836
|
+
request.signal.throwIfAborted();
|
|
837
|
+
const token = await abortable(options.getSessionToken(), request.signal);
|
|
838
|
+
const headers = new Headers(request.headers);
|
|
839
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
840
|
+
headers.set("Accept-Language", locale.replaceAll("_", "-"));
|
|
841
|
+
let authenticated;
|
|
842
|
+
try {
|
|
843
|
+
authenticated = new Request(request, {
|
|
844
|
+
headers,
|
|
845
|
+
...options.ambient ? {} : { redirect: "error" }
|
|
846
|
+
});
|
|
847
|
+
} catch {
|
|
848
|
+
throw new FluidBridgeNetworkError();
|
|
849
|
+
}
|
|
850
|
+
let response;
|
|
851
|
+
try {
|
|
852
|
+
response = await options.nativeFetch(authenticated);
|
|
853
|
+
} catch {
|
|
854
|
+
if (request.signal.aborted) throw request.signal.reason;
|
|
855
|
+
throw new FluidBridgeNetworkError();
|
|
856
|
+
}
|
|
857
|
+
if (!options.ambient && (response.status === 401 || response.status === 403)) throw new FluidBridgeFailedAuthenticationError();
|
|
858
|
+
return response;
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
function documentOrigin(baseUrl) {
|
|
862
|
+
try {
|
|
863
|
+
return new URL(baseUrl).origin;
|
|
864
|
+
} catch {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function resolveSignal(input, init) {
|
|
869
|
+
if (init?.signal !== void 0) return init.signal ?? void 0;
|
|
870
|
+
return input instanceof Request ? input.signal : void 0;
|
|
871
|
+
}
|
|
872
|
+
function abortable(promise, signal) {
|
|
873
|
+
return new Promise((resolve, reject) => {
|
|
874
|
+
const abort = () => reject(signal.reason);
|
|
875
|
+
if (signal.aborted) {
|
|
876
|
+
abort();
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
880
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
//#endregion
|
|
884
|
+
//#region src/canonical-client.ts
|
|
885
|
+
const activeDocuments = /* @__PURE__ */ new WeakSet();
|
|
886
|
+
/** One canonical client per iframe document. Tokens are decoded for delivery binding only. */
|
|
887
|
+
function createFluidBridge(options) {
|
|
888
|
+
if (!options || typeof options.clientId !== "string" || !options.clientId.trim() || "appOrigins" in options) throw new FluidBridgeConfigurationError("Use clientId and the server-delivered origin list");
|
|
889
|
+
const hostOrigin = exactOrigin(options.hostOrigin);
|
|
890
|
+
const current = options.targetWindow ?? globalThis.window;
|
|
891
|
+
if (!current || current.parent === current) throw new FluidBridgeHostUnavailableError();
|
|
892
|
+
const parent = current.parent;
|
|
893
|
+
const nativeFetch = options.fetch ?? current.fetch?.bind(current);
|
|
894
|
+
const timeout = options.timeoutMs ?? 1e4;
|
|
895
|
+
if (typeof nativeFetch !== "function" || !Number.isFinite(timeout) || timeout < 1 || timeout > 6e4) throw new FluidBridgeConfigurationError("A fetch implementation and bounded timeout are required");
|
|
896
|
+
if (activeDocuments.has(current)) throw new FluidBridgeConfigurationError("Destroy the existing bridge before initializing another");
|
|
897
|
+
const bootstrap = new URL(current.location.href);
|
|
898
|
+
if (bootstrap.searchParams.has("session_token")) {
|
|
899
|
+
bootstrap.searchParams.delete("session_token");
|
|
900
|
+
current.history.replaceState(current.history.state, "", bootstrap.href);
|
|
901
|
+
}
|
|
902
|
+
activeDocuments.add(current);
|
|
903
|
+
const readyId = uuidV7();
|
|
904
|
+
const pending = /* @__PURE__ */ new Map();
|
|
905
|
+
let context;
|
|
906
|
+
let sid;
|
|
907
|
+
let destroyed = false;
|
|
908
|
+
let settled = false;
|
|
909
|
+
let handshakeFailed = false;
|
|
910
|
+
let refreshInFlight;
|
|
911
|
+
let attempts = 0;
|
|
912
|
+
let retryTimer;
|
|
913
|
+
let refreshTimer;
|
|
914
|
+
let resolveHandshake;
|
|
915
|
+
let rejectHandshake;
|
|
916
|
+
const handshake = new Promise((resolve, reject) => {
|
|
917
|
+
resolveHandshake = resolve;
|
|
918
|
+
rejectHandshake = reject;
|
|
919
|
+
});
|
|
920
|
+
handshake.catch(() => void 0);
|
|
921
|
+
const startHandshakeTimer = () => setTimeout(() => failHandshake(new FluidBridgeHandshakeTimeoutError()), timeout);
|
|
922
|
+
let handshakeTimer = startHandshakeTimer();
|
|
923
|
+
function handshakeState() {
|
|
924
|
+
if (destroyed) return "destroyed";
|
|
925
|
+
if (handshakeFailed) return "failed";
|
|
926
|
+
return context ? "resolved" : "pending";
|
|
927
|
+
}
|
|
928
|
+
function post(envelope) {
|
|
929
|
+
if (destroyed) return;
|
|
930
|
+
try {
|
|
931
|
+
parent.postMessage(envelope, hostOrigin);
|
|
932
|
+
} catch {
|
|
933
|
+
rejectRequest(envelope.request_id, new FluidBridgeHostUnavailableError());
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function failHandshake(error) {
|
|
937
|
+
if (settled) return;
|
|
938
|
+
settled = true;
|
|
939
|
+
handshakeFailed = true;
|
|
940
|
+
clearTimeout(handshakeTimer);
|
|
941
|
+
clearTimeout(retryTimer);
|
|
942
|
+
rejectHandshake(error);
|
|
943
|
+
}
|
|
944
|
+
function sendReady(force = false) {
|
|
945
|
+
if (destroyed || settled || attempts >= 8) return;
|
|
946
|
+
if (context && !force) return;
|
|
947
|
+
post({
|
|
948
|
+
type: "bridge.ready",
|
|
949
|
+
payload: {},
|
|
950
|
+
request_id: readyId,
|
|
951
|
+
version: "1"
|
|
952
|
+
});
|
|
953
|
+
attempts += 1;
|
|
954
|
+
if (!context && !settled) retryTimer = setTimeout(sendReady, Math.min(100 * 2 ** (attempts - 1), 1600));
|
|
955
|
+
}
|
|
956
|
+
async function getSessionToken() {
|
|
957
|
+
await handshake;
|
|
958
|
+
if (destroyed) throw new FluidBridgeHostUnavailableError();
|
|
959
|
+
if (refreshInFlight) return refreshInFlight;
|
|
960
|
+
const requestId = uuidV7();
|
|
961
|
+
refreshInFlight = new Promise((resolve, reject) => {
|
|
962
|
+
const timer = setTimeout(() => {
|
|
963
|
+
pending.delete(requestId);
|
|
964
|
+
reject(new FluidBridgeRequestTimeoutError());
|
|
965
|
+
}, timeout);
|
|
966
|
+
pending.set(requestId, {
|
|
967
|
+
resolve,
|
|
968
|
+
reject,
|
|
969
|
+
timer
|
|
970
|
+
});
|
|
971
|
+
post({
|
|
972
|
+
type: "session_token.refresh_request",
|
|
973
|
+
payload: {
|
|
974
|
+
clientId: options.clientId,
|
|
975
|
+
sdkVersion: APP_BRIDGE_SDK_VERSION,
|
|
976
|
+
protocolVersion: "1"
|
|
977
|
+
},
|
|
978
|
+
request_id: requestId,
|
|
979
|
+
version: "1"
|
|
980
|
+
});
|
|
981
|
+
}).finally(() => {
|
|
982
|
+
refreshInFlight = void 0;
|
|
983
|
+
});
|
|
984
|
+
return refreshInFlight;
|
|
985
|
+
}
|
|
986
|
+
const DEFAULT_TOKEN_LIFETIME_MS = 6e4;
|
|
987
|
+
function scheduleRefresh(claims) {
|
|
988
|
+
clearTimeout(refreshTimer);
|
|
989
|
+
const lifetimeMs = typeof claims.iat === "number" && Number.isSafeInteger(claims.iat) && claims.iat < claims.exp ? (claims.exp - claims.iat) * 1e3 : DEFAULT_TOKEN_LIFETIME_MS;
|
|
990
|
+
const delay = Math.max(1e3, lifetimeMs / 2, lifetimeMs - 1e4);
|
|
991
|
+
refreshTimer = setTimeout(() => {
|
|
992
|
+
getSessionToken().catch(() => void 0);
|
|
993
|
+
}, Math.min(2147483647, delay));
|
|
994
|
+
}
|
|
995
|
+
function onMessage(event) {
|
|
996
|
+
if (destroyed || event.source !== parent || event.origin !== hostOrigin) return;
|
|
997
|
+
const parsed = parseBridgeEnvelope(event.data, { receivedBy: "iframe" });
|
|
998
|
+
const id = parsed.ok ? parsed.envelope.request_id : parsed.requestId;
|
|
999
|
+
if (!id || id !== readyId && !pending.has(id)) return;
|
|
1000
|
+
if (id === readyId && settled) return;
|
|
1001
|
+
if (!parsed.ok) {
|
|
1002
|
+
rejectRequest(id, parsed.reason === "version_mismatch" ? new FluidBridgeIncompatibleVersionError() : new FluidBridgeInvalidContextError());
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const envelope = parsed.envelope;
|
|
1006
|
+
if (envelope.type === "error") rejectRequest(id, errorFromHost(envelope.payload));
|
|
1007
|
+
else if (envelope.type === "context.deliver" && id === readyId && !context) {
|
|
1008
|
+
const candidate = envelope.payload;
|
|
1009
|
+
if (candidate.app.clientId !== options.clientId || !candidate.store.uuid || !candidate.actor.uuid || !candidate.entry.name || !candidate.fetch.allowedOrigins.includes(current.location.origin)) {
|
|
1010
|
+
failHandshake(new FluidBridgeInvalidContextError());
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (!supportsSdk("0.1.0", candidate.sdk.minVersion)) {
|
|
1014
|
+
failHandshake(new FluidBridgeIncompatibleVersionError(void 0, {
|
|
1015
|
+
received: APP_BRIDGE_SDK_VERSION,
|
|
1016
|
+
required: candidate.sdk.minVersion
|
|
1017
|
+
}));
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
context = freezeContext(candidate);
|
|
1021
|
+
clearTimeout(retryTimer);
|
|
1022
|
+
} else if (envelope.type === "session_token.deliver") {
|
|
1023
|
+
const claims = boundToken(envelope.payload.sessionToken, context, sid);
|
|
1024
|
+
if (!claims) {
|
|
1025
|
+
rejectRequest(id, new FluidBridgeFailedAuthenticationError());
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
sid ??= claims.sid;
|
|
1029
|
+
scheduleRefresh(claims);
|
|
1030
|
+
if (id === readyId && context) {
|
|
1031
|
+
settled = true;
|
|
1032
|
+
clearTimeout(handshakeTimer);
|
|
1033
|
+
resolveHandshake(context);
|
|
1034
|
+
} else {
|
|
1035
|
+
const request = pending.get(id);
|
|
1036
|
+
if (request) {
|
|
1037
|
+
clearTimeout(request.timer);
|
|
1038
|
+
pending.delete(id);
|
|
1039
|
+
request.resolve(envelope.payload.sessionToken);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function rejectRequest(id, error) {
|
|
1045
|
+
if (id === readyId) failHandshake(error);
|
|
1046
|
+
const request = pending.get(id);
|
|
1047
|
+
if (request) {
|
|
1048
|
+
clearTimeout(request.timer);
|
|
1049
|
+
pending.delete(id);
|
|
1050
|
+
request.reject(error);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
const getContext = async () => {
|
|
1054
|
+
if (destroyed) throw new FluidBridgeHostUnavailableError();
|
|
1055
|
+
return handshake;
|
|
1056
|
+
};
|
|
1057
|
+
const getBaseUrl = () => current.location.href;
|
|
1058
|
+
const getDeliveredContext = () => context;
|
|
1059
|
+
const authenticatedFetch = createAuthenticatedFetch({
|
|
1060
|
+
nativeFetch,
|
|
1061
|
+
getContext,
|
|
1062
|
+
getSessionToken,
|
|
1063
|
+
getBaseUrl,
|
|
1064
|
+
getHandshakeState: handshakeState,
|
|
1065
|
+
getDeliveredContext,
|
|
1066
|
+
ambient: false
|
|
1067
|
+
});
|
|
1068
|
+
const ambientFetch = createAuthenticatedFetch({
|
|
1069
|
+
nativeFetch,
|
|
1070
|
+
getContext,
|
|
1071
|
+
getSessionToken,
|
|
1072
|
+
getBaseUrl,
|
|
1073
|
+
getHandshakeState: handshakeState,
|
|
1074
|
+
getDeliveredContext,
|
|
1075
|
+
ambient: true
|
|
1076
|
+
});
|
|
1077
|
+
const previousFetch = current.fetch;
|
|
1078
|
+
if (!options.disableFetchPatch) current.fetch = ambientFetch;
|
|
1079
|
+
function destroy() {
|
|
1080
|
+
if (destroyed) return;
|
|
1081
|
+
destroyed = true;
|
|
1082
|
+
activeDocuments.delete(current);
|
|
1083
|
+
current.removeEventListener("message", onMessage);
|
|
1084
|
+
current.removeEventListener("pagehide", onPageHide);
|
|
1085
|
+
current.removeEventListener("pageshow", onPageShow);
|
|
1086
|
+
current.removeEventListener("unload", destroy);
|
|
1087
|
+
clearTimeout(retryTimer);
|
|
1088
|
+
clearTimeout(refreshTimer);
|
|
1089
|
+
failHandshake(new FluidBridgeHostUnavailableError());
|
|
1090
|
+
for (const request of pending.values()) {
|
|
1091
|
+
clearTimeout(request.timer);
|
|
1092
|
+
request.reject(new FluidBridgeHostUnavailableError());
|
|
1093
|
+
}
|
|
1094
|
+
pending.clear();
|
|
1095
|
+
context = void 0;
|
|
1096
|
+
sid = void 0;
|
|
1097
|
+
if (current.fetch === ambientFetch) current.fetch = previousFetch;
|
|
1098
|
+
}
|
|
1099
|
+
function onPageHide(event) {
|
|
1100
|
+
if (event.persisted) return;
|
|
1101
|
+
destroy();
|
|
1102
|
+
}
|
|
1103
|
+
function onPageShow(event) {
|
|
1104
|
+
if (!event.persisted || destroyed || settled) return;
|
|
1105
|
+
clearTimeout(handshakeTimer);
|
|
1106
|
+
handshakeTimer = startHandshakeTimer();
|
|
1107
|
+
clearTimeout(retryTimer);
|
|
1108
|
+
attempts = 0;
|
|
1109
|
+
sendReady(true);
|
|
1110
|
+
}
|
|
1111
|
+
current.addEventListener("message", onMessage);
|
|
1112
|
+
current.addEventListener("pagehide", onPageHide);
|
|
1113
|
+
current.addEventListener("pageshow", onPageShow);
|
|
1114
|
+
current.addEventListener("unload", destroy);
|
|
1115
|
+
sendReady();
|
|
1116
|
+
return {
|
|
1117
|
+
getContext,
|
|
1118
|
+
getSessionToken,
|
|
1119
|
+
fetch: authenticatedFetch,
|
|
1120
|
+
destroy
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
function exactOrigin(origin) {
|
|
1124
|
+
try {
|
|
1125
|
+
const url = new URL(origin);
|
|
1126
|
+
if ((url.protocol === "http:" || url.protocol === "https:") && url.origin === origin) return origin;
|
|
1127
|
+
} catch {}
|
|
1128
|
+
throw new FluidBridgeConfigurationError("hostOrigin must be an exact HTTP(S) origin");
|
|
1129
|
+
}
|
|
1130
|
+
function uuidV7() {
|
|
1131
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
1132
|
+
let timestamp = BigInt(Date.now());
|
|
1133
|
+
for (let index = 5; index >= 0; index -= 1) {
|
|
1134
|
+
bytes[index] = Number(timestamp & 255n);
|
|
1135
|
+
timestamp >>= 8n;
|
|
1136
|
+
}
|
|
1137
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
1138
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1139
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1140
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1141
|
+
}
|
|
1142
|
+
function supportsSdk(received, minimum) {
|
|
1143
|
+
const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
1144
|
+
if (!semver.test(received) || !semver.test(minimum)) return false;
|
|
1145
|
+
const left = received.split(".").map(Number), right = minimum.split(".").map(Number);
|
|
1146
|
+
if (![...left, ...right].every(Number.isSafeInteger)) return false;
|
|
1147
|
+
for (let index = 0; index < 3; index += 1) if (left[index] !== right[index]) return left[index] > right[index];
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
function boundToken(token, context, sid) {
|
|
1151
|
+
try {
|
|
1152
|
+
if (!context || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) return;
|
|
1153
|
+
const encoded = token.split(".")[1];
|
|
1154
|
+
const claims = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(atob(encoded.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0))));
|
|
1155
|
+
if (!claims || typeof claims !== "object" || Array.isArray(claims)) return;
|
|
1156
|
+
const payload = claims;
|
|
1157
|
+
const entry = payload.entry;
|
|
1158
|
+
if (payload.aud !== context.app.clientId || payload.dest !== `urn:fluid:store:${context.store.uuid}` || payload.sub !== `urn:fluid:${context.actor.type}:${context.actor.uuid}` || typeof payload.sid !== "string" || !payload.sid || sid !== void 0 && payload.sid !== sid || typeof payload.exp !== "number" || !Number.isSafeInteger(payload.exp) || typeof entry !== "object" || !entry || !("type" in entry) || !("name" in entry) || entry.type !== context.entry.type || entry.name !== context.entry.name) return;
|
|
1159
|
+
const iat = typeof payload.iat === "number" && Number.isSafeInteger(payload.iat) ? payload.iat : void 0;
|
|
1160
|
+
return iat === void 0 ? {
|
|
1161
|
+
sid: payload.sid,
|
|
1162
|
+
exp: payload.exp
|
|
1163
|
+
} : {
|
|
1164
|
+
sid: payload.sid,
|
|
1165
|
+
exp: payload.exp,
|
|
1166
|
+
iat
|
|
1167
|
+
};
|
|
1168
|
+
} catch {
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
function freezeContext(context) {
|
|
1173
|
+
Object.freeze(context.store);
|
|
1174
|
+
Object.freeze(context.app);
|
|
1175
|
+
Object.freeze(context.entry);
|
|
1176
|
+
if (context.actor.roles) Object.freeze(context.actor.roles);
|
|
1177
|
+
if (context.actor.permissions) Object.freeze(context.actor.permissions);
|
|
1178
|
+
Object.freeze(context.actor);
|
|
1179
|
+
context.resources.forEach(Object.freeze);
|
|
1180
|
+
Object.freeze(context.resources);
|
|
1181
|
+
Object.freeze(context.fetch.allowedOrigins);
|
|
1182
|
+
Object.freeze(context.fetch);
|
|
1183
|
+
Object.freeze(context.sdk);
|
|
1184
|
+
return Object.freeze(context);
|
|
1185
|
+
}
|
|
1186
|
+
function errorFromHost(payload) {
|
|
1187
|
+
switch (payload.code) {
|
|
1188
|
+
case "version_mismatch":
|
|
1189
|
+
case "incompatible_version": return new FluidBridgeIncompatibleVersionError(void 0, versionDetails(payload.details));
|
|
1190
|
+
case "unauthorized": return new FluidBridgeUnauthorizedError();
|
|
1191
|
+
case "failed_authentication": return new FluidBridgeFailedAuthenticationError();
|
|
1192
|
+
case "invalid_context": return new FluidBridgeInvalidContextError();
|
|
1193
|
+
case "request_timeout": return new FluidBridgeRequestTimeoutError();
|
|
1194
|
+
case "handshake_timeout": return new FluidBridgeHandshakeTimeoutError();
|
|
1195
|
+
case "host_unavailable": return new FluidBridgeHostUnavailableError();
|
|
1196
|
+
case "forbidden_origin": return new FluidBridgeForbiddenOriginError();
|
|
1197
|
+
default: return new FluidBridgeTokenMintError();
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function versionDetails(details) {
|
|
1201
|
+
if (!details) return void 0;
|
|
1202
|
+
const sanitize = (value) => {
|
|
1203
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1204
|
+
const record = value;
|
|
1205
|
+
return Object.fromEntries(["sdkVersion", "protocolVersion"].flatMap((key) => typeof record[key] === "string" ? [[key, record[key].slice(0, 32)]] : []));
|
|
1206
|
+
};
|
|
1207
|
+
return {
|
|
1208
|
+
received: sanitize(details.received),
|
|
1209
|
+
required: sanitize(details.required)
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
//#endregion
|
|
1213
|
+
exports.APP_BRIDGE_SDK_NAME = APP_BRIDGE_SDK_NAME;
|
|
1214
|
+
exports.APP_BRIDGE_SDK_VERSION = APP_BRIDGE_SDK_VERSION;
|
|
1215
|
+
exports.ConfigurationError = FluidBridgeConfigurationError;
|
|
1216
|
+
exports.FLUID_BRIDGE_PROTOCOL_VERSION = FLUID_BRIDGE_PROTOCOL_VERSION;
|
|
1217
|
+
exports.FailedAuthenticationError = FluidBridgeFailedAuthenticationError;
|
|
1218
|
+
exports.FetchTargetNotAllowedError = FluidBridgeFetchTargetError;
|
|
1219
|
+
exports.FluidBridgeConfigurationError = FluidBridgeConfigurationError;
|
|
1220
|
+
exports.FluidBridgeError = FluidBridgeError;
|
|
1221
|
+
exports.FluidBridgeFailedAuthenticationError = FluidBridgeFailedAuthenticationError;
|
|
1222
|
+
exports.FluidBridgeFetchTargetError = FluidBridgeFetchTargetError;
|
|
1223
|
+
exports.FluidBridgeForbiddenOriginError = FluidBridgeForbiddenOriginError;
|
|
1224
|
+
exports.FluidBridgeHandshakeTimeoutError = FluidBridgeHandshakeTimeoutError;
|
|
1225
|
+
exports.FluidBridgeHostUnavailableError = FluidBridgeHostUnavailableError;
|
|
1226
|
+
exports.FluidBridgeIncompatibleVersionError = FluidBridgeIncompatibleVersionError;
|
|
1227
|
+
exports.FluidBridgeInvalidContextError = FluidBridgeInvalidContextError;
|
|
1228
|
+
exports.FluidBridgeMessageType = FluidBridgeMessageType;
|
|
1229
|
+
exports.FluidBridgeNetworkError = FluidBridgeNetworkError;
|
|
1230
|
+
exports.FluidBridgeRequestTimeoutError = FluidBridgeRequestTimeoutError;
|
|
1231
|
+
exports.FluidBridgeTokenMintError = FluidBridgeTokenMintError;
|
|
1232
|
+
exports.FluidBridgeUnauthorizedError = FluidBridgeUnauthorizedError;
|
|
1233
|
+
exports.ForbiddenOriginError = FluidBridgeForbiddenOriginError;
|
|
1234
|
+
exports.HandshakeTimeoutError = FluidBridgeHandshakeTimeoutError;
|
|
1235
|
+
exports.HostUnavailableError = FluidBridgeHostUnavailableError;
|
|
1236
|
+
exports.IncompatibleVersionError = FluidBridgeIncompatibleVersionError;
|
|
1237
|
+
exports.InvalidContextError = FluidBridgeInvalidContextError;
|
|
1238
|
+
exports.NetworkError = FluidBridgeNetworkError;
|
|
1239
|
+
exports.RequestTimeoutError = FluidBridgeRequestTimeoutError;
|
|
1240
|
+
exports.TokenMintFailedError = FluidBridgeTokenMintError;
|
|
1241
|
+
exports.UnauthorizedError = FluidBridgeUnauthorizedError;
|
|
1242
|
+
exports.createCanonicalBridgeHost = createCanonicalBridgeHost;
|
|
1243
|
+
exports.createFluidBridge = createFluidBridge;
|
|
1244
|
+
exports.createFluidBridgeHost = createFluidBridgeHost;
|
|
1245
|
+
exports.mintAppBridgeSessionToken = mintAppBridgeSessionToken;
|
|
1246
|
+
exports.sessionTokenRequestFromContext = sessionTokenRequestFromContext;
|
|
1247
|
+
|
|
1248
|
+
//# sourceMappingURL=index.cjs.map
|