@cef-ai/wallet 1.0.0 → 1.1.0
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 +99 -4
- package/dist/index.cjs +243 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +180 -53
- package/dist/index.d.ts +180 -53
- package/dist/index.js +243 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -105,8 +105,15 @@ var PopupTransport = class {
|
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Close the popup if open. Immediately rejects any in-flight `request()`
|
|
110
|
+
* calls (mirrors `IframeTransport.close()`) rather than leaving them to
|
|
111
|
+
* time out — a caller that closes the popup mid-request (e.g.
|
|
112
|
+
* `EmbedWallet.register()`'s `finally` block) gets a prompt rejection
|
|
113
|
+
* instead of waiting out the full default 60s timeout.
|
|
114
|
+
*/
|
|
109
115
|
close() {
|
|
116
|
+
this.rejectAllPending("transport closed");
|
|
110
117
|
if (this.popup && !this.popup.closed) {
|
|
111
118
|
this.popup.close();
|
|
112
119
|
}
|
|
@@ -130,6 +137,14 @@ var PopupTransport = class {
|
|
|
130
137
|
}
|
|
131
138
|
}
|
|
132
139
|
// ---- internal -------------------------------------------------------------
|
|
140
|
+
/** Reject every in-flight request with a `WalletError('internal', reason)`. Idempotent. */
|
|
141
|
+
rejectAllPending(reason) {
|
|
142
|
+
for (const [id, pending] of this.pending) {
|
|
143
|
+
clearTimeout(pending.timer);
|
|
144
|
+
this.pending.delete(id);
|
|
145
|
+
pending.reject(new WalletError("internal", reason));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
133
148
|
ensureListening() {
|
|
134
149
|
if (this.listener) return;
|
|
135
150
|
this.listener = (event) => this.onMessage(event);
|
|
@@ -187,6 +202,173 @@ function defaultOpener(url, features) {
|
|
|
187
202
|
const opened = w == null ? void 0 : w.open(url, "_blank", features);
|
|
188
203
|
return opened;
|
|
189
204
|
}
|
|
205
|
+
var IframeTransport = class {
|
|
206
|
+
constructor(options) {
|
|
207
|
+
this.iframe = null;
|
|
208
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
209
|
+
this.listener = null;
|
|
210
|
+
this.ready = false;
|
|
211
|
+
this.outbox = [];
|
|
212
|
+
this.backdrop = null;
|
|
213
|
+
this.hiddenCssText = "";
|
|
214
|
+
this.overlayVisible = false;
|
|
215
|
+
this.opts = __spreadValues({
|
|
216
|
+
hostWindow: globalThis.window,
|
|
217
|
+
document: globalThis.document,
|
|
218
|
+
defaultTimeoutMs: 6e4,
|
|
219
|
+
mountIframe: options.mountIframe,
|
|
220
|
+
onOverlay: options.onOverlay
|
|
221
|
+
}, options);
|
|
222
|
+
}
|
|
223
|
+
ensureIframe() {
|
|
224
|
+
var _a, _b, _c;
|
|
225
|
+
if (this.iframe) return;
|
|
226
|
+
const hostOrigin = (_b = (_a = globalThis.location) == null ? void 0 : _a.origin) != null ? _b : "";
|
|
227
|
+
const url = `${this.opts.walletOrigin}/embed/wallet?origin=${encodeURIComponent(hostOrigin)}`;
|
|
228
|
+
const allow = `publickey-credentials-get ${this.opts.walletOrigin}`;
|
|
229
|
+
if (this.opts.mountIframe) {
|
|
230
|
+
this.iframe = this.opts.mountIframe(url, allow);
|
|
231
|
+
} else {
|
|
232
|
+
const el = this.opts.document.createElement("iframe");
|
|
233
|
+
el.src = url;
|
|
234
|
+
el.setAttribute("allow", allow);
|
|
235
|
+
el.style.cssText = "position:fixed;border:0;width:0;height:0;left:-9999px;";
|
|
236
|
+
this.opts.document.body.appendChild(el);
|
|
237
|
+
this.iframe = el;
|
|
238
|
+
}
|
|
239
|
+
this.hiddenCssText = (_c = this.iframe.style.cssText) != null ? _c : "";
|
|
240
|
+
this.ready = false;
|
|
241
|
+
this.outbox = [];
|
|
242
|
+
this.overlayVisible = false;
|
|
243
|
+
this.ensureListening();
|
|
244
|
+
}
|
|
245
|
+
showOverlay() {
|
|
246
|
+
var _a, _b;
|
|
247
|
+
if (this.overlayVisible) return;
|
|
248
|
+
this.overlayVisible = true;
|
|
249
|
+
if (this.iframe) {
|
|
250
|
+
const backdrop = this.opts.document.createElement("div");
|
|
251
|
+
backdrop.setAttribute("data-wallet-overlay-backdrop", "");
|
|
252
|
+
backdrop.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:2147483646;";
|
|
253
|
+
this.opts.document.body.appendChild(backdrop);
|
|
254
|
+
this.backdrop = backdrop;
|
|
255
|
+
this.iframe.style.cssText = "position:fixed;border:0;z-index:2147483647;width:min(420px,100vw);height:min(640px,100vh);left:50%;top:50%;transform:translate(-50%,-50%);";
|
|
256
|
+
}
|
|
257
|
+
(_b = (_a = this.opts).onOverlay) == null ? void 0 : _b.call(_a, true);
|
|
258
|
+
}
|
|
259
|
+
hideOverlay() {
|
|
260
|
+
var _a, _b, _c;
|
|
261
|
+
if (!this.overlayVisible) return;
|
|
262
|
+
this.overlayVisible = false;
|
|
263
|
+
(_a = this.backdrop) == null ? void 0 : _a.remove();
|
|
264
|
+
this.backdrop = null;
|
|
265
|
+
if (this.iframe) {
|
|
266
|
+
this.iframe.style.cssText = this.hiddenCssText;
|
|
267
|
+
}
|
|
268
|
+
(_c = (_b = this.opts).onOverlay) == null ? void 0 : _c.call(_b, false);
|
|
269
|
+
}
|
|
270
|
+
ensureListening() {
|
|
271
|
+
if (this.listener) return;
|
|
272
|
+
this.listener = (e) => this.onMessage(e);
|
|
273
|
+
this.opts.hostWindow.addEventListener("message", this.listener);
|
|
274
|
+
}
|
|
275
|
+
request(_0, _1) {
|
|
276
|
+
return __async(this, arguments, function* (message, expectedTypes, init = {}) {
|
|
277
|
+
var _a;
|
|
278
|
+
this.ensureIframe();
|
|
279
|
+
const id = message.id;
|
|
280
|
+
const timeoutMs = (_a = init.timeoutMs) != null ? _a : this.opts.defaultTimeoutMs;
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
const timer = setTimeout(() => {
|
|
283
|
+
if (this.pending.has(id)) {
|
|
284
|
+
this.pending.delete(id);
|
|
285
|
+
this.outbox = this.outbox.filter((m) => m.id !== id);
|
|
286
|
+
reject(new WalletError("internal", `request "${message.type}" (id=${id}) timed out after ${timeoutMs}ms`));
|
|
287
|
+
}
|
|
288
|
+
}, timeoutMs);
|
|
289
|
+
this.pending.set(id, { expectedTypes, resolve: (m) => resolve(m), reject, timer });
|
|
290
|
+
if (this.ready) this.postToIframe(message);
|
|
291
|
+
else this.outbox.push(message);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
postToIframe(message) {
|
|
296
|
+
var _a, _b;
|
|
297
|
+
(_b = (_a = this.iframe) == null ? void 0 : _a.contentWindow) == null ? void 0 : _b.postMessage(message, this.opts.walletOrigin);
|
|
298
|
+
}
|
|
299
|
+
close() {
|
|
300
|
+
var _a, _b;
|
|
301
|
+
this.rejectAllPending("transport closed");
|
|
302
|
+
(_a = this.backdrop) == null ? void 0 : _a.remove();
|
|
303
|
+
this.backdrop = null;
|
|
304
|
+
this.overlayVisible = false;
|
|
305
|
+
(_b = this.iframe) == null ? void 0 : _b.remove();
|
|
306
|
+
this.iframe = null;
|
|
307
|
+
this.ready = false;
|
|
308
|
+
this.outbox = [];
|
|
309
|
+
}
|
|
310
|
+
destroy() {
|
|
311
|
+
this.close();
|
|
312
|
+
if (this.listener) {
|
|
313
|
+
this.opts.hostWindow.removeEventListener("message", this.listener);
|
|
314
|
+
this.listener = null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
rejectAllPending(reason) {
|
|
318
|
+
for (const [id, p] of this.pending) {
|
|
319
|
+
clearTimeout(p.timer);
|
|
320
|
+
this.pending.delete(id);
|
|
321
|
+
p.reject(new WalletError("internal", reason));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
onMessage(event) {
|
|
325
|
+
const data = event.data;
|
|
326
|
+
if (!isMatchingOrigin(event.origin, this.opts.walletOrigin)) {
|
|
327
|
+
if (data && typeof data.id === "string" && this.pending.has(data.id)) {
|
|
328
|
+
const p2 = this.pending.get(data.id);
|
|
329
|
+
this.pending.delete(data.id);
|
|
330
|
+
clearTimeout(p2.timer);
|
|
331
|
+
p2.reject(
|
|
332
|
+
new WalletError(
|
|
333
|
+
"origin-mismatch",
|
|
334
|
+
`message from "${event.origin}" rejected (expected "${this.opts.walletOrigin}")`
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!data || typeof data.type !== "string" || typeof data.id !== "string") return;
|
|
341
|
+
if (data.type === "wallet:ready") {
|
|
342
|
+
if (this.ready) return;
|
|
343
|
+
this.ready = true;
|
|
344
|
+
const flush = this.outbox;
|
|
345
|
+
this.outbox = [];
|
|
346
|
+
for (const m of flush) this.postToIframe(m);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (data.type === "wallet:ui:show") {
|
|
350
|
+
this.showOverlay();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (data.type === "wallet:ui:hide") {
|
|
354
|
+
this.hideOverlay();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const p = this.pending.get(data.id);
|
|
358
|
+
if (!p) return;
|
|
359
|
+
clearTimeout(p.timer);
|
|
360
|
+
this.pending.delete(data.id);
|
|
361
|
+
if (data.type === "wallet:error") {
|
|
362
|
+
p.reject(new WalletError(data.code, data.message, data.traceId));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (!p.expectedTypes.includes(data.type)) {
|
|
366
|
+
p.reject(new WalletError("internal", `received unexpected response type "${data.type}" for id "${data.id}"`));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
p.resolve(data);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
190
372
|
|
|
191
373
|
// src/EmbedWallet.ts
|
|
192
374
|
var EmbedWallet = class {
|
|
@@ -194,7 +376,7 @@ var EmbedWallet = class {
|
|
|
194
376
|
this._addresses = null;
|
|
195
377
|
this._credentialId = null;
|
|
196
378
|
this._listeners = {};
|
|
197
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
379
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
198
380
|
const injectedTransport = (_a = opts.__internal__) == null ? void 0 : _a.transport;
|
|
199
381
|
if (!injectedTransport && !opts.walletOrigin) {
|
|
200
382
|
throw new Error("EmbedWallet: `walletOrigin` is required (e.g. https://wallet.example.com).");
|
|
@@ -203,9 +385,8 @@ var EmbedWallet = class {
|
|
|
203
385
|
this.walletOrigin = (_b = opts.walletOrigin) != null ? _b : "";
|
|
204
386
|
this.popup = { width: (_d = (_c = opts.popup) == null ? void 0 : _c.width) != null ? _d : 420, height: (_f = (_e = opts.popup) == null ? void 0 : _e.height) != null ? _f : 640 };
|
|
205
387
|
this.appName = (_g = opts.appName) != null ? _g : "";
|
|
206
|
-
this.
|
|
207
|
-
|
|
208
|
-
});
|
|
388
|
+
this.regPopupOpener = (_h = opts.__internal__) == null ? void 0 : _h.registerPopupOpener;
|
|
389
|
+
this.transport = injectedTransport != null ? injectedTransport : opts.transport === "popup" ? new PopupTransport({ walletOrigin: this.walletOrigin }) : new IframeTransport({ walletOrigin: this.walletOrigin });
|
|
209
390
|
}
|
|
210
391
|
get isLoggedIn() {
|
|
211
392
|
return this._addresses !== null;
|
|
@@ -216,9 +397,27 @@ var EmbedWallet = class {
|
|
|
216
397
|
get credentialId() {
|
|
217
398
|
return this._credentialId;
|
|
218
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Safari blocks WebAuthn `create()` in cross-origin iframes, so `register()`
|
|
402
|
+
* always runs its `wallet:hello` ceremony over a `PopupTransport` — even
|
|
403
|
+
* when the configured transport is the iframe. When the main transport is
|
|
404
|
+
* already a `PopupTransport` it's reused; otherwise a transient one is
|
|
405
|
+
* opened synchronously (within the caller's click) and closed afterward.
|
|
406
|
+
* `login()` and all other ops use the configured transport unchanged.
|
|
407
|
+
*/
|
|
219
408
|
register() {
|
|
220
409
|
return __async(this, arguments, function* (opts = {}) {
|
|
221
|
-
|
|
410
|
+
if (this.transport instanceof PopupTransport) {
|
|
411
|
+
return this.helloAndAwaitLogin("register", opts);
|
|
412
|
+
}
|
|
413
|
+
const popup = new PopupTransport(__spreadValues({
|
|
414
|
+
walletOrigin: this.walletOrigin
|
|
415
|
+
}, this.regPopupOpener ? { openPopup: this.regPopupOpener } : {}));
|
|
416
|
+
try {
|
|
417
|
+
return yield this.helloAndAwaitLogin("register", opts, popup);
|
|
418
|
+
} finally {
|
|
419
|
+
popup.close();
|
|
420
|
+
}
|
|
222
421
|
});
|
|
223
422
|
}
|
|
224
423
|
login() {
|
|
@@ -377,16 +576,18 @@ var EmbedWallet = class {
|
|
|
377
576
|
return { appId: this.appId, name: this.appName, origin };
|
|
378
577
|
}
|
|
379
578
|
helloAndAwaitLogin(_0) {
|
|
380
|
-
return __async(this, arguments, function* (intent, opts = {}) {
|
|
579
|
+
return __async(this, arguments, function* (intent, opts = {}, transport = this.transport) {
|
|
381
580
|
const id = crypto.randomUUID();
|
|
382
581
|
const helloMessage = {
|
|
383
582
|
type: "wallet:hello",
|
|
384
583
|
id,
|
|
385
584
|
appContext: this.appContext(),
|
|
386
585
|
intent,
|
|
387
|
-
label: opts.label
|
|
586
|
+
label: opts.label,
|
|
587
|
+
name: opts.name,
|
|
588
|
+
email: opts.email
|
|
388
589
|
};
|
|
389
|
-
const result = yield
|
|
590
|
+
const result = yield transport.request(helloMessage, [
|
|
390
591
|
"wallet:login:ok"
|
|
391
592
|
]);
|
|
392
593
|
this._addresses = result.addresses;
|
|
@@ -419,7 +620,8 @@ var PopupHostBridge = class {
|
|
|
419
620
|
this.handlers = {};
|
|
420
621
|
this.listener = null;
|
|
421
622
|
this.opts = __spreadValues({
|
|
422
|
-
popupWindow: globalThis.window
|
|
623
|
+
popupWindow: globalThis.window,
|
|
624
|
+
replyTo: "opener"
|
|
423
625
|
}, options);
|
|
424
626
|
}
|
|
425
627
|
/** Register a handler for a specific message type. Replaces any prior handler. */
|
|
@@ -428,16 +630,16 @@ var PopupHostBridge = class {
|
|
|
428
630
|
return this;
|
|
429
631
|
}
|
|
430
632
|
/**
|
|
431
|
-
* Send a `PopupToHost` message back to the opener
|
|
432
|
-
* remember the host-window reference itself —
|
|
433
|
-
* each call, so it survives popup re-opens.
|
|
633
|
+
* Send a `PopupToHost` message back to the opener or parent, depending on
|
|
634
|
+
* `replyTo`. The bridge does not remember the host-window reference itself —
|
|
635
|
+
* it reads the target window each call, so it survives popup re-opens.
|
|
434
636
|
*/
|
|
435
637
|
send(message) {
|
|
436
|
-
const
|
|
437
|
-
if (!
|
|
638
|
+
const target = this.opts.replyTo === "parent" ? globalThis.parent : globalThis.opener;
|
|
639
|
+
if (!target || typeof target.postMessage !== "function") {
|
|
438
640
|
return;
|
|
439
641
|
}
|
|
440
|
-
|
|
642
|
+
target.postMessage(message, this.opts.hostOrigin);
|
|
441
643
|
}
|
|
442
644
|
/**
|
|
443
645
|
* Start listening for inbound messages.
|
|
@@ -523,8 +725,8 @@ var BroadcastSessionShare = class {
|
|
|
523
725
|
});
|
|
524
726
|
return;
|
|
525
727
|
}
|
|
526
|
-
if (msg.type === "
|
|
527
|
-
(_b = (_a = this.opts).
|
|
728
|
+
if (msg.type === "session-available") {
|
|
729
|
+
(_b = (_a = this.opts).onAvailable) == null ? void 0 : _b.call(_a);
|
|
528
730
|
return;
|
|
529
731
|
}
|
|
530
732
|
};
|
|
@@ -563,7 +765,13 @@ var BroadcastSessionShare = class {
|
|
|
563
765
|
});
|
|
564
766
|
});
|
|
565
767
|
}
|
|
566
|
-
/**
|
|
768
|
+
/**
|
|
769
|
+
* Begin offering this tab's session in response to requests, and invoke
|
|
770
|
+
* `onAvailable` on incoming `session-available` pushes. Callers that only
|
|
771
|
+
* care about `onAvailable` (no session to offer) still call `start()` —
|
|
772
|
+
* `getSession()` returning `null` simply means `request-session` replies
|
|
773
|
+
* are skipped.
|
|
774
|
+
*/
|
|
567
775
|
start() {
|
|
568
776
|
if (this.listening) return;
|
|
569
777
|
this.listening = true;
|
|
@@ -573,10 +781,24 @@ var BroadcastSessionShare = class {
|
|
|
573
781
|
this.channel.removeEventListener("message", this.onMessage);
|
|
574
782
|
this.listening = false;
|
|
575
783
|
}
|
|
576
|
-
/**
|
|
784
|
+
/**
|
|
785
|
+
* Notify peers that the user logged out. Kept for public-API compatibility;
|
|
786
|
+
* no `BroadcastSessionShare` instance currently handles the receive side
|
|
787
|
+
* (see class doc) — cross-tab logout is `CrossTabSync`'s responsibility.
|
|
788
|
+
*/
|
|
577
789
|
broadcastLogout() {
|
|
578
790
|
this.channel.postMessage({ type: "logout" });
|
|
579
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Notify peers that this tab just established a session (register/login
|
|
794
|
+
* completed). Carries NO key material — it is purely a push trigger that
|
|
795
|
+
* tells session-less peers to issue their own `request()`. Key material
|
|
796
|
+
* still only ever moves as an `offer-session` reply to an explicit
|
|
797
|
+
* `request-session`; `announce()` does not bypass that gate.
|
|
798
|
+
*/
|
|
799
|
+
announce() {
|
|
800
|
+
this.channel.postMessage({ type: "session-available" });
|
|
801
|
+
}
|
|
580
802
|
/** Free the underlying channel handle. Call from unload handlers. */
|
|
581
803
|
close() {
|
|
582
804
|
this.stop();
|
|
@@ -584,6 +806,6 @@ var BroadcastSessionShare = class {
|
|
|
584
806
|
}
|
|
585
807
|
};
|
|
586
808
|
|
|
587
|
-
export { ALLOWED_ORIGIN_SCHEMES, BROADCAST_CHANNEL_NAME, BroadcastSessionShare, EmbedWallet, PopupHostBridge, PopupTransport, isMatchingOrigin };
|
|
809
|
+
export { ALLOWED_ORIGIN_SCHEMES, BROADCAST_CHANNEL_NAME, BroadcastSessionShare, EmbedWallet, IframeTransport, PopupHostBridge, PopupTransport, isMatchingOrigin };
|
|
588
810
|
//# sourceMappingURL=index.js.map
|
|
589
811
|
//# sourceMappingURL=index.js.map
|