@openkey/sdk 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,19 +20,336 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ KeyIdTypeMismatchError: () => KeyIdTypeMismatchError,
23
24
  OpenKey: () => OpenKey,
25
+ OpenKeyNostr: () => OpenKeyNostr,
24
26
  OpenKeyProvider: () => OpenKeyProvider,
25
27
  default: () => index_default,
26
- getOpenKey: () => getOpenKey
28
+ getOpenKey: () => getOpenKey,
29
+ resolveAuthorizeTinyCloudRouting: () => resolveAuthorizeTinyCloudRouting,
30
+ shouldRouteAuthorizeTinyCloudExternal: () => shouldRouteAuthorizeTinyCloudExternal,
31
+ validateIframeResize: () => validateIframeResize
27
32
  });
28
33
  module.exports = __toCommonJS(index_exports);
29
- var import_core = require("@openkey/core");
34
+
35
+ // ../core/dist/index.js
36
+ var u = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
37
+ function g(e) {
38
+ let r = "", n = e.length;
39
+ for (let t = 0; t < n; t += 3) {
40
+ let c = e[t], d = t + 1 < n ? e[t + 1] : 0, i = t + 2 < n ? e[t + 2] : 0, o = c << 16 | d << 8 | i;
41
+ r += u[o >> 18 & 63], r += u[o >> 12 & 63], r += t + 1 < n ? u[o >> 6 & 63] : "", r += t + 2 < n ? u[o & 63] : "";
42
+ }
43
+ return r.replace(/\+/g, "-").replace(/\//g, "_");
44
+ }
45
+ function y() {
46
+ let e = new Uint8Array(32);
47
+ return crypto.getRandomValues(e), g(e);
48
+ }
49
+ async function x(e) {
50
+ let n = new TextEncoder().encode(e), t = await crypto.subtle.digest("SHA-256", n);
51
+ return new Uint8Array(t);
52
+ }
53
+ async function k(e, r) {
54
+ let t = await (r ?? x)(e);
55
+ return g(t);
56
+ }
57
+ function m() {
58
+ let e = new Uint8Array(16);
59
+ return crypto.getRandomValues(e), g(e);
60
+ }
61
+ function T(e) {
62
+ let { host: r, clientId: n, redirectUri: t, codeChallenge: c, state: d, scopes: i, authorizePath: o = "/api/auth/oauth2/authorize" } = e, s = new URL(o, r);
63
+ return s.searchParams.set("client_id", n), s.searchParams.set("redirect_uri", t), s.searchParams.set("response_type", "code"), s.searchParams.set("scope", i.join(" ")), s.searchParams.set("state", d), s.searchParams.set("code_challenge", c), s.searchParams.set("code_challenge_method", "S256"), s.toString();
64
+ }
65
+
66
+ // src/nostr.ts
67
+ var NOSTR_FRAME_TIMEOUT_MS = 5 * 60 * 1e3;
68
+ var NOSTR_PROTOCOL_VERSION = 1;
69
+ function newRequestId() {
70
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
71
+ return crypto.randomUUID();
72
+ }
73
+ if (typeof crypto === "undefined" || typeof crypto.getRandomValues !== "function") {
74
+ throw new Error("Secure request-id generation is unavailable.");
75
+ }
76
+ const bytes = new Uint32Array(4);
77
+ crypto.getRandomValues(bytes);
78
+ return `nostr-${Array.from(bytes, (value) => value.toString(16)).join("")}`;
79
+ }
80
+ var NostrFrame = class {
81
+ root;
82
+ shadow;
83
+ iframe;
84
+ outer;
85
+ host;
86
+ messageHandler = () => {
87
+ };
88
+ revealed = false;
89
+ requestedHeight = 400;
90
+ viewportHandler = () => {
91
+ this.syncViewport();
92
+ this.applyFrameHeight();
93
+ };
94
+ constructor(host, url) {
95
+ this.host = host;
96
+ this.root = document.createElement("div");
97
+ this.shadow = this.root.attachShadow({ mode: "closed" });
98
+ const style = document.createElement("style");
99
+ style.textContent = `
100
+ :host{all:initial}
101
+ .nf-outer{position:fixed;top:-9999px;left:-9999px;width:1px;height:1px;overflow:hidden;opacity:0;pointer-events:none}
102
+ .nf-outer.nf-visible{position:fixed;top:var(--nf-viewport-top,0px);left:var(--nf-viewport-left,0px);width:var(--nf-viewport-width,100vw);height:var(--nf-viewport-height,100dvh);box-sizing:border-box;padding:max(16px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));overflow:auto;overscroll-behavior:contain;opacity:1;pointer-events:auto;background:rgba(0,0,0,0.42);z-index:999999;display:flex;align-items:center;justify-content:center}
103
+ .nf-card{width:1px;height:1px;overflow:hidden}
104
+ .nf-visible .nf-card{background:#fafafa;width:min(400px,100%);height:auto;max-height:100%;border-radius:16px;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);overflow:auto;flex:0 1 auto}
105
+ iframe{border:none;width:1px;height:1px;display:block;transition:height 200ms ease}
106
+ .nf-visible iframe{width:100%;height:400px;max-height:100%}
107
+ @media (prefers-reduced-motion:reduce){iframe{transition:none}}
108
+ `;
109
+ this.outer = document.createElement("div");
110
+ this.outer.className = "nf-outer";
111
+ this.outer.setAttribute("role", "dialog");
112
+ this.outer.setAttribute("aria-modal", "true");
113
+ this.outer.setAttribute("aria-label", "OpenKey approval");
114
+ this.outer.setAttribute("aria-hidden", "true");
115
+ const card = document.createElement("div");
116
+ card.className = "nf-card";
117
+ this.iframe = document.createElement("iframe");
118
+ this.iframe.src = url;
119
+ this.iframe.title = "OpenKey approval";
120
+ this.iframe.setAttribute("allow", "clipboard-write *; publickey-credentials-get *; publickey-credentials-create *");
121
+ this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
122
+ card.appendChild(this.iframe);
123
+ this.outer.appendChild(card);
124
+ this.shadow.appendChild(style);
125
+ this.shadow.appendChild(this.outer);
126
+ document.body.appendChild(this.root);
127
+ this.syncViewport();
128
+ window.addEventListener("resize", this.viewportHandler);
129
+ window.visualViewport?.addEventListener("resize", this.viewportHandler);
130
+ window.visualViewport?.addEventListener("scroll", this.viewportHandler);
131
+ }
132
+ onMessage(handler) {
133
+ this.messageHandler = (event) => {
134
+ if (event.origin !== this.host) return;
135
+ if (event.source !== this.iframe.contentWindow) return;
136
+ handler(event.data);
137
+ };
138
+ window.addEventListener("message", this.messageHandler);
139
+ }
140
+ reveal() {
141
+ if (this.revealed) return;
142
+ this.revealed = true;
143
+ this.syncViewport();
144
+ this.outer.classList.add("nf-visible");
145
+ this.outer.setAttribute("aria-hidden", "false");
146
+ this.applyFrameHeight();
147
+ }
148
+ resize(height) {
149
+ if (!Number.isFinite(height) || height <= 0) return;
150
+ this.requestedHeight = height;
151
+ if (this.revealed) this.applyFrameHeight();
152
+ }
153
+ syncViewport() {
154
+ const viewport = window.visualViewport;
155
+ this.outer.style.setProperty("--nf-viewport-top", `${viewport?.offsetTop ?? 0}px`);
156
+ this.outer.style.setProperty("--nf-viewport-left", `${viewport?.offsetLeft ?? 0}px`);
157
+ this.outer.style.setProperty("--nf-viewport-width", `${viewport?.width ?? window.innerWidth}px`);
158
+ this.outer.style.setProperty("--nf-viewport-height", `${viewport?.height ?? window.innerHeight}px`);
159
+ }
160
+ applyFrameHeight() {
161
+ if (!this.revealed) return;
162
+ const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
163
+ const max = Math.max(1, Math.floor(viewportHeight - 32));
164
+ this.iframe.style.height = `${Math.min(this.requestedHeight, max)}px`;
165
+ }
166
+ postMessage(message) {
167
+ this.iframe.contentWindow?.postMessage(message, this.host);
168
+ }
169
+ destroy() {
170
+ window.removeEventListener("message", this.messageHandler);
171
+ window.removeEventListener("resize", this.viewportHandler);
172
+ window.visualViewport?.removeEventListener("resize", this.viewportHandler);
173
+ window.visualViewport?.removeEventListener("scroll", this.viewportHandler);
174
+ this.root.remove();
175
+ }
176
+ };
177
+ var OpenKeyNostr = class {
178
+ constructor(host) {
179
+ this.host = host;
180
+ }
181
+ /**
182
+ * Get-or-create the user's OpenKey-custodied Nostr identity. Always shows
183
+ * a visible consent card (identity creation, or - when `relayUrl` is
184
+ * given - a relay-scoped kind:22242/NIP-42 grant for that exact relay).
185
+ * Never resolves with the secret key; only `keyId`/`pubkey`/`npub`.
186
+ */
187
+ async connect(opts) {
188
+ const url = this.widgetUrl();
189
+ return this.run(
190
+ url,
191
+ { type: "openkey:nostr:connect:request", relayUrl: opts?.relayUrl },
192
+ (data, resolve, reject) => {
193
+ if (data.type !== "openkey:nostr:connect:response") return false;
194
+ if (data.success) resolve({ keyId: data.keyId, pubkey: data.pubkey, npub: data.npub });
195
+ else reject(data.error);
196
+ return true;
197
+ }
198
+ );
199
+ }
200
+ /**
201
+ * Sign a NIP-01 event template with the user's OpenKey Nostr key. Silent
202
+ * (no visible UI) when an active grant already covers this client origin
203
+ * + key + kind (+ exact relay, for kind 22242). Otherwise shows a visible
204
+ * consent card before signing - required for every first kind:9 message to
205
+ * a new origin, per NIP-29 channel-message policy.
206
+ */
207
+ async signEvent(keyId, event) {
208
+ const url = this.widgetUrl();
209
+ return this.run(
210
+ url,
211
+ { type: "openkey:nostr:sign:request", keyId, event },
212
+ (data, resolve, reject) => {
213
+ if (data.type !== "openkey:nostr:sign:response") return false;
214
+ if (data.success) resolve(data.event);
215
+ else reject(data.error);
216
+ return true;
217
+ }
218
+ );
219
+ }
220
+ widgetUrl() {
221
+ const origin = encodeURIComponent(window.location.origin);
222
+ return `${this.host}/widget/embed/nostr/approve?origin=${origin}`;
223
+ }
224
+ run(url, request, onData) {
225
+ return new Promise((resolve, reject) => {
226
+ let settled = false;
227
+ const frame = new NostrFrame(this.host, url);
228
+ const requestId = newRequestId();
229
+ const settle = (fn) => {
230
+ if (settled) return;
231
+ settled = true;
232
+ clearTimeout(timeout);
233
+ fn();
234
+ frame.destroy();
235
+ };
236
+ frame.onMessage((data) => {
237
+ if (!data || typeof data.type !== "string") return;
238
+ if (data.type === "openkey:ready") {
239
+ if (data.protocolVersion !== NOSTR_PROTOCOL_VERSION) return;
240
+ frame.postMessage({ ...request, requestId, protocolVersion: NOSTR_PROTOCOL_VERSION });
241
+ return;
242
+ }
243
+ if (data.requestId !== requestId || data.protocolVersion !== NOSTR_PROTOCOL_VERSION) return;
244
+ if (data.type === "openkey:resize") {
245
+ frame.resize(data.height);
246
+ return;
247
+ }
248
+ if (data.type === "openkey:nostr:show") {
249
+ frame.reveal();
250
+ return;
251
+ }
252
+ if (data.type === "openkey:close") {
253
+ settle(() => reject({ code: "USER_CANCELLED", message: "Request closed" }));
254
+ return;
255
+ }
256
+ onData(data, (v) => settle(() => resolve(v)), (e) => settle(() => reject(e)));
257
+ });
258
+ const timeout = setTimeout(() => {
259
+ settle(() => reject({ code: "TIMEOUT", message: "Nostr request timed out" }));
260
+ }, NOSTR_FRAME_TIMEOUT_MS);
261
+ });
262
+ }
263
+ };
264
+
265
+ // src/index.ts
30
266
  var DEFAULT_HOST = "https://openkey.so";
31
267
  var POPUP_WIDTH = 400;
32
268
  var POPUP_HEIGHT = 600;
33
269
  var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
34
270
  var IFRAME_READY_TIMEOUT = 3e3;
35
271
  var OAUTH_STORAGE_KEY = "openkey_oauth";
272
+ function validateIframeResize(incoming, expected) {
273
+ if (!incoming || typeof incoming !== "object") return null;
274
+ const data = incoming;
275
+ if (data.type !== "openkey:resize") return null;
276
+ if (expected.requestId === null && expected.protocolVersion === null) {
277
+ if (data.requestId !== void 0) return null;
278
+ } else {
279
+ if (expected.requestId === null || expected.protocolVersion === null) return null;
280
+ if (typeof data.protocolVersion !== "number" || data.protocolVersion !== expected.protocolVersion) {
281
+ return null;
282
+ }
283
+ if (typeof data.requestId !== "string" || data.requestId !== expected.requestId) {
284
+ return null;
285
+ }
286
+ }
287
+ if (typeof data.height !== "number" || !Number.isFinite(data.height) || data.height <= 0) {
288
+ return null;
289
+ }
290
+ return Math.min(data.height, Math.floor(expected.viewportHeight * 0.85));
291
+ }
292
+ function shouldRouteAuthorizeTinyCloudExternal(state) {
293
+ return state.lastAuth?.keyType === "EXTERNAL";
294
+ }
295
+ var KeyIdTypeMismatchError = class extends Error {
296
+ code;
297
+ requestKeyId;
298
+ lastAuthKeyId;
299
+ requestKeyType;
300
+ lastAuthKeyType;
301
+ constructor(fields) {
302
+ super(fields.message);
303
+ this.name = "KeyIdTypeMismatchError";
304
+ this.code = "KEY_ID_TYPE_MISMATCH";
305
+ this.requestKeyId = fields.requestKeyId;
306
+ this.lastAuthKeyId = fields.lastAuthKeyId;
307
+ this.requestKeyType = fields.requestKeyType;
308
+ this.lastAuthKeyType = fields.lastAuthKeyType;
309
+ }
310
+ };
311
+ function resolveAuthorizeTinyCloudRouting(state) {
312
+ const lastAuthKeyId = state.lastAuth?.keyId ?? null;
313
+ const lastAuthKeyType = state.lastAuth?.keyType ?? null;
314
+ const rawRequestKeyId = state.requestKeyId ?? null;
315
+ const requestKeyId = rawRequestKeyId && rawRequestKeyId.length > 0 ? rawRequestKeyId : null;
316
+ const knownKeys = state.knownKeys ?? [];
317
+ const lookupType = (id) => {
318
+ for (const k2 of knownKeys) {
319
+ if (k2.id === id) return k2.keyType;
320
+ if (k2.address && k2.address.toLowerCase() === id.toLowerCase()) return k2.keyType;
321
+ }
322
+ if (id.startsWith("external:")) return "EXTERNAL";
323
+ return null;
324
+ };
325
+ if (!requestKeyId) {
326
+ const route = lastAuthKeyType === "EXTERNAL" ? "external" : "managed";
327
+ return {
328
+ route,
329
+ resolvedKeyId: lastAuthKeyId,
330
+ resolvedKeyType: lastAuthKeyType
331
+ };
332
+ }
333
+ let requestKeyType = lookupType(requestKeyId);
334
+ if (!requestKeyType && lastAuthKeyId && requestKeyId === lastAuthKeyId) {
335
+ requestKeyType = lastAuthKeyType;
336
+ }
337
+ if (requestKeyType && lastAuthKeyType && requestKeyType !== lastAuthKeyType) {
338
+ throw new KeyIdTypeMismatchError({
339
+ message: `authorizeTinyCloud: request.keyId ${requestKeyId} resolves to ${requestKeyType} but active session key ${lastAuthKeyId} is ${lastAuthKeyType}; refuse to route silently`,
340
+ requestKeyId,
341
+ lastAuthKeyId,
342
+ requestKeyType,
343
+ lastAuthKeyType
344
+ });
345
+ }
346
+ const effectiveType = requestKeyType ?? lastAuthKeyType;
347
+ return {
348
+ route: effectiveType === "EXTERNAL" ? "external" : "managed",
349
+ resolvedKeyId: requestKeyId,
350
+ resolvedKeyType: effectiveType
351
+ };
352
+ }
36
353
  var IframeModal = class {
37
354
  root;
38
355
  shadow;
@@ -41,6 +358,13 @@ var IframeModal = class {
41
358
  onMessage;
42
359
  messageHandler;
43
360
  host;
361
+ // Sol final continuation contract requirement 5: when the modal carries a
362
+ // versioned request, iframe resize traffic MUST correlate to its
363
+ // requestId AND protocolVersion. Set by `setExpectedCorrelation` as soon
364
+ // as the modal is created. When both are null the flow is unversioned and
365
+ // only uncorrelated resizes from this iframe are accepted (TC-647).
366
+ expectedRequestId = null;
367
+ expectedProtocolVersion = null;
44
368
  constructor(opts) {
45
369
  this.host = opts.host;
46
370
  this.onClose = opts.onClose;
@@ -96,10 +420,17 @@ var IframeModal = class {
96
420
  if (event.source !== this.iframe.contentWindow) return;
97
421
  const data = event.data;
98
422
  if (data.type === "openkey:resize") {
99
- const h = Math.min(data.height, Math.floor(window.innerHeight * 0.85));
423
+ const h = validateIframeResize(event.data, {
424
+ requestId: this.expectedRequestId,
425
+ protocolVersion: this.expectedProtocolVersion,
426
+ viewportHeight: window.innerHeight
427
+ });
428
+ if (h === null) return;
100
429
  this.iframe.style.height = `${h}px`;
101
430
  return;
102
431
  }
432
+ if (data.type === "openkey:close") {
433
+ }
103
434
  this.onMessage(data);
104
435
  };
105
436
  window.addEventListener("message", this.messageHandler);
@@ -108,6 +439,21 @@ var IframeModal = class {
108
439
  postMessage(message) {
109
440
  this.iframe.contentWindow?.postMessage(message, this.host);
110
441
  }
442
+ /**
443
+ * Sol final continuation contract requirement 5: bind the active
444
+ * request correlation so subsequent resize (and future correlated)
445
+ * messages can be verified against `requestId` + `protocolVersion`.
446
+ *
447
+ * The outer flow calls this right after creating the modal for a
448
+ * versioned request, before the iframe can send anything, so no
449
+ * uncorrelated resize is ever accepted for that request. Passing `null`
450
+ * for both arguments means the flow is unversioned; passing `null` for
451
+ * only one drops every resize.
452
+ */
453
+ setExpectedCorrelation(requestId, protocolVersion) {
454
+ this.expectedRequestId = requestId;
455
+ this.expectedProtocolVersion = protocolVersion;
456
+ }
111
457
  destroy() {
112
458
  window.removeEventListener("message", this.messageHandler);
113
459
  this.root.remove();
@@ -241,12 +587,23 @@ var OpenKey = class {
241
587
  lastAuth = null;
242
588
  discoveredProviders = [];
243
589
  sessionToken = null;
590
+ /** Cancels widget flows that could outlive a sign-out on this instance. */
591
+ activeFlowCancellations = /* @__PURE__ */ new Set();
592
+ /**
593
+ * Nostr identity custody + signing (secp256k1 Schnorr / BIP-340). Kept
594
+ * fully separate from the Ethereum flows above: it never reuses
595
+ * `lastAuth`/`sessionToken`, and its widget messages never carry a
596
+ * sessionToken - the OpenKey session and any signing grants stay inside
597
+ * the OpenKey-origin iframe for the whole lifetime of this client.
598
+ */
599
+ nostr;
244
600
  constructor(config = {}) {
245
601
  this.config = config;
246
602
  this.host = config.host || DEFAULT_HOST;
247
603
  this.oauthHost = config.oauthHost || this.deriveOAuthHost(this.host);
248
604
  this.appName = config.appName || window.location.hostname;
249
605
  this.mode = config.mode ?? "iframe";
606
+ this.nostr = new OpenKeyNostr(this.host);
250
607
  if (typeof window !== "undefined") {
251
608
  this.discoveredProviders = [];
252
609
  window.addEventListener("eip6963:announceProvider", (event) => {
@@ -272,6 +629,37 @@ var OpenKey = class {
272
629
  return "https://api.openkey.so";
273
630
  }
274
631
  }
632
+ /**
633
+ * Sol MAJOR-4 (final continuation): fetch the caller's registered keys
634
+ * so `resolveAuthorizeTinyCloudRouting` can resolve an explicit pin
635
+ * whose keyId does not match the active session key. Called only when
636
+ * the caller passes a `requestKeyId` we do not already recognise; the
637
+ * common no-pin path skips the network round-trip.
638
+ *
639
+ * Failures do NOT block routing — we fall through with `knownKeys=[]`
640
+ * so the resolver's lastAuth-based fallback still runs. If the caller
641
+ * pinned a genuinely unknown key that turns out to be external, the
642
+ * widget rejects the server-signing path and the SDK surfaces that
643
+ * error from the finalize call rather than silently routing wrong.
644
+ */
645
+ async fetchKnownKeysIfNeeded(requestKeyId) {
646
+ if (!requestKeyId) return [];
647
+ const la = this.lastAuth;
648
+ if (la?.keyId === requestKeyId) return [];
649
+ if (la?.address && la.address.toLowerCase() === requestKeyId.toLowerCase()) return [];
650
+ if (requestKeyId.startsWith("external:")) return [];
651
+ try {
652
+ const res = await fetch(`${this.oauthHost}/api/keys`, {
653
+ method: "GET",
654
+ credentials: "include"
655
+ });
656
+ if (!res.ok) return [];
657
+ const body = await res.json();
658
+ return Array.isArray(body?.keys) ? body.keys : [];
659
+ } catch {
660
+ return [];
661
+ }
662
+ }
275
663
  /**
276
664
  * Connect to OpenKey and get user's wallet address
277
665
  * Opens auth flow for user to select/create a key
@@ -284,6 +672,36 @@ var OpenKey = class {
284
672
  this.lastAuth = result;
285
673
  return result;
286
674
  }
675
+ /**
676
+ * Signs the active OpenKey account out through the OpenKey-owned widget.
677
+ *
678
+ * The SDK passes its Better Auth bearer through the OpenKey-owned widget
679
+ * protocol so applications do not have to implement revocation themselves.
680
+ * Local SDK session state is cleared before opening the widget, including
681
+ * when remote revocation is unavailable.
682
+ */
683
+ async signOut(opts) {
684
+ const requestId = `ok-signout-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
685
+ const sessionToken = this.sessionToken || void 0;
686
+ const mode = this.resolveMode(opts?.mode) === "redirect" ? "popup" : opts?.mode;
687
+ this.cancelActiveFlows();
688
+ this.lastAuth = null;
689
+ this.sessionToken = null;
690
+ const acknowledgement = await this.openFlow(
691
+ "sign-out",
692
+ {
693
+ type: "openkey:sign-out:request",
694
+ requestId,
695
+ protocolVersion: 1,
696
+ sessionToken
697
+ },
698
+ mode
699
+ );
700
+ if (acknowledgement.requestId !== requestId) {
701
+ throw new Error("OpenKey signOut received an acknowledgement for a different request");
702
+ }
703
+ return acknowledgement;
704
+ }
287
705
  /**
288
706
  * Better-auth session token relayed during connect(), or null if the
289
707
  * flow did not provide one. Use as a bearer token for API calls made
@@ -322,6 +740,208 @@ var OpenKey = class {
322
740
  }
323
741
  return this.signWithOpenKey(request, opts?.mode);
324
742
  }
743
+ /**
744
+ * Versioned TinyCloud authorization protocol (v1).
745
+ *
746
+ * Unlike `signMessage()` (which is contractually byte-exact — the returned
747
+ * signature always verifies against the caller's exact bytes), this method
748
+ * permits the user to narrow the requested capabilities before signing.
749
+ * OpenKey may therefore regenerate the SIWE. The response carries
750
+ * `signedMessage` — the exact bytes the signature actually verifies against.
751
+ *
752
+ * TinyCloud completes the session with `signedMessage`, not the original
753
+ * `siwe` passed in. Consumers MUST use the returned bytes and MUST NOT
754
+ * assume the returned grants are a superset of what they requested — the
755
+ * user is allowed to remove any grant that is not required.
756
+ *
757
+ * Legacy `signMessage()` callers are unaffected: their exact-byte contract
758
+ * remains preserved and OpenKey never rewrites their bytes.
759
+ */
760
+ async authorizeTinyCloud(request, opts) {
761
+ if (request.protocolVersion !== 1) {
762
+ throw new Error(
763
+ `authorizeTinyCloud only supports protocolVersion 1; got ${request.protocolVersion}`
764
+ );
765
+ }
766
+ if (typeof request.siwe !== "string" || !request.siwe) {
767
+ throw new Error("authorizeTinyCloud requires a non-empty SIWE");
768
+ }
769
+ let routing;
770
+ try {
771
+ const knownKeys = await this.fetchKnownKeysIfNeeded(request.keyId);
772
+ routing = resolveAuthorizeTinyCloudRouting({
773
+ lastAuth: this.lastAuth,
774
+ requestKeyId: request.keyId,
775
+ knownKeys
776
+ });
777
+ } catch (e) {
778
+ if (e instanceof KeyIdTypeMismatchError) throw e;
779
+ throw e instanceof Error ? e : new Error("authorizeTinyCloud: failed to resolve target key type");
780
+ }
781
+ if (routing.route === "external") {
782
+ return this.authorizeTinyCloudExternal(request, opts);
783
+ }
784
+ const requestId = `ok-auth-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
785
+ const resolvedKeyId = routing.resolvedKeyId ?? request.keyId ?? this.lastAuth?.keyId;
786
+ const raw = await this.openFlow(
787
+ "sign",
788
+ {
789
+ type: "openkey:sign:request",
790
+ requestId,
791
+ protocolVersion: 1,
792
+ message: request.siwe,
793
+ keyId: resolvedKeyId,
794
+ // Forward JWK so the widget can pass it to /authorize-sign for
795
+ // narrowed-SIWE regeneration.
796
+ jwk: request.jwk,
797
+ // Forward the TinyCloud host so the widget can bind it into the
798
+ // /authorize-sign-prepare context (Sol MAJOR-2).
799
+ host: request.host,
800
+ // Sol MAJOR-2 (envelope): forward the caller-supplied presentation
801
+ // envelope so the widget can render honest provenance and the
802
+ // /authorize-sign-prepare route can bind it into the authorization
803
+ // context. The envelope is display-only; the widget transport
804
+ // validates and size-bounds it before use and NEVER treats it as
805
+ // verified unless the server actually origin-binds a manifest.
806
+ presentation: request.presentation,
807
+ sessionToken: this.sessionToken || void 0
808
+ },
809
+ opts?.mode
810
+ );
811
+ if (typeof raw.signedMessage !== "string" || !raw.signedMessage) {
812
+ throw new Error(
813
+ "authorizeTinyCloud: widget returned no signedMessage \u2014 signature would not correspond to the displayed authorization"
814
+ );
815
+ }
816
+ if (!Array.isArray(raw.selectedActionKeys)) {
817
+ throw new Error(
818
+ "authorizeTinyCloud: widget returned no selectedActionKeys \u2014 cannot verify displayed authorization matches the signed bytes"
819
+ );
820
+ }
821
+ if (!Array.isArray(raw.permissions)) {
822
+ throw new Error(
823
+ "authorizeTinyCloud: widget returned no permissions \u2014 cannot verify displayed authorization matches the signed bytes"
824
+ );
825
+ }
826
+ return {
827
+ protocolVersion: 1,
828
+ address: raw.address,
829
+ signature: raw.signature,
830
+ signedMessage: raw.signedMessage,
831
+ selectedActionKeys: raw.selectedActionKeys,
832
+ permissions: raw.permissions
833
+ };
834
+ }
835
+ /**
836
+ * Sol MAJOR-3 (continuation): authorizeTinyCloud path for external keys.
837
+ * The wallet lives outside OpenKey, so the SDK opens the shared widget
838
+ * for review + preview, then invokes the user's wallet to sign the
839
+ * preview bytes, then finalizes server-side via /authorize-sign.
840
+ *
841
+ * Flow:
842
+ * 1. Open the widget with `externalSign: true` — the widget renders
843
+ * the shared SigningApproval UI, calls /authorize-sign-prepare,
844
+ * calls /authorize-sign-preview after the user approves a
845
+ * selection, and hands back `{ previewApprovalToken,
846
+ * signedMessage, selectedActionIds, address, authorizationContextToken }`.
847
+ * 2. SDK invokes the user's wallet on the returned signedMessage.
848
+ * 3. SDK POSTs /authorize-sign with `externalSignature`.
849
+ *
850
+ * Legacy exact-byte signMessage callers are unaffected — this path is
851
+ * only taken via authorizeTinyCloud (protocolVersion:1) when the target
852
+ * key is EXTERNAL. Sol's rejection required this path to go through
853
+ * the same review UI as managed keys.
854
+ */
855
+ async authorizeTinyCloudExternal(request, opts) {
856
+ const keyId = request.keyId ?? this.lastAuth?.keyId;
857
+ if (!keyId) {
858
+ throw new Error(
859
+ "authorizeTinyCloud (external) requires a keyId \u2014 call connect() first or pass request.keyId"
860
+ );
861
+ }
862
+ const walletAddress = this.lastAuth?.address;
863
+ if (!walletAddress) {
864
+ throw new Error(
865
+ "authorizeTinyCloud (external) requires a connected wallet \u2014 call connect() first"
866
+ );
867
+ }
868
+ const requestId = `ok-ext-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
869
+ const approval = await this.openFlow(
870
+ "sign",
871
+ {
872
+ type: "openkey:sign:request",
873
+ requestId,
874
+ protocolVersion: 1,
875
+ message: request.siwe,
876
+ keyId,
877
+ jwk: request.jwk,
878
+ host: request.host,
879
+ // Sol MAJOR-2 (envelope): forward the caller-supplied presentation
880
+ // envelope. Same rules as the managed path — display-only, size-
881
+ // bounded at the transport, never treated as verified without
882
+ // server-side origin-binding.
883
+ presentation: request.presentation,
884
+ sessionToken: this.sessionToken || void 0,
885
+ // Sol MAJOR-3 (continuation): tell the widget to hand us back
886
+ // the previewApproval instead of asking OpenKey to sign
887
+ // server-side with a managed key. The widget will still render
888
+ // the SAME SigningApproval component; it just diverges at the
889
+ // "sign" step and emits `openkey:externalSign:approve` back
890
+ // through the transport.
891
+ externalSign: true
892
+ },
893
+ opts?.mode
894
+ );
895
+ if (!approval?.previewApprovalToken || !approval?.signedMessage) {
896
+ throw new Error(
897
+ "authorizeTinyCloud (external): widget did not return a previewApprovalToken + signedMessage"
898
+ );
899
+ }
900
+ if (approval.address && approval.address.toLowerCase() !== walletAddress.toLowerCase()) {
901
+ throw new Error(
902
+ `authorizeTinyCloud (external): widget preview address ${approval.address} does not match connected wallet ${walletAddress}`
903
+ );
904
+ }
905
+ const provider = await this.findWalletProvider(walletAddress);
906
+ const hexMessage = this.toHex(approval.signedMessage);
907
+ const walletSignature = await provider.request({
908
+ method: "personal_sign",
909
+ params: [hexMessage, walletAddress]
910
+ });
911
+ const finalizeRes = await fetch(
912
+ `${this.oauthHost}/api/delegate/authorize-sign`,
913
+ {
914
+ method: "POST",
915
+ credentials: "include",
916
+ headers: { "Content-Type": "application/json" },
917
+ body: JSON.stringify({
918
+ authorizationContextToken: approval.authorizationContextToken,
919
+ previewApprovalToken: approval.previewApprovalToken,
920
+ selectedActionIds: approval.selectedActionIds,
921
+ protocolVersion: 1,
922
+ externalSignature: walletSignature
923
+ })
924
+ }
925
+ );
926
+ if (!finalizeRes.ok) {
927
+ const body = await finalizeRes.json().catch(() => ({ error: `HTTP ${finalizeRes.status}` }));
928
+ throw new Error(body.error || `authorize-sign failed (${finalizeRes.status})`);
929
+ }
930
+ const finalize = await finalizeRes.json();
931
+ if (finalize.signedMessage !== approval.signedMessage) {
932
+ throw new Error(
933
+ "authorizeTinyCloud (external): server signed different bytes than the preview showed"
934
+ );
935
+ }
936
+ return {
937
+ protocolVersion: 1,
938
+ address: finalize.address,
939
+ signature: finalize.signature,
940
+ signedMessage: finalize.signedMessage,
941
+ selectedActionKeys: finalize.selectedActionKeys ?? [],
942
+ permissions: finalize.permissions ?? []
943
+ };
944
+ }
325
945
  /**
326
946
  * Sign typed data (EIP-712) with the user's OpenKey wallet
327
947
  * For external keys, routes directly to the user's wallet provider
@@ -456,14 +1076,14 @@ var OpenKey = class {
456
1076
  * @returns Promise that resolves with authorization code after user consent
457
1077
  */
458
1078
  connect: async (config) => {
459
- const verifier = (0, import_core.generateCodeVerifier)();
460
- const challenge = await (0, import_core.generateCodeChallenge)(verifier);
461
- const state = config.state || (0, import_core.generateState)();
1079
+ const verifier = y();
1080
+ const challenge = await k(verifier);
1081
+ const state = config.state || m();
462
1082
  sessionStorage.setItem(
463
1083
  OAUTH_STORAGE_KEY,
464
1084
  JSON.stringify({ verifier, state })
465
1085
  );
466
- const authUrl = (0, import_core.buildAuthorizationUrl)({
1086
+ const authUrl = T({
467
1087
  host: this.oauthHost,
468
1088
  clientId: config.clientId,
469
1089
  redirectUri: config.redirectUri,
@@ -635,7 +1255,7 @@ var OpenKey = class {
635
1255
  const eoaFlag = action === "connect" && this.hasDetectedEoa() ? "&hasEoa=true" : "";
636
1256
  if (mode === "popup") {
637
1257
  const url2 = `${this.host}/widget/${action}?origin=${origin}${eoaFlag}`;
638
- return new Promise((resolve, reject) => this.openPopup(url2, message, resolve, reject));
1258
+ return new Promise((resolve, reject) => this.openPopup(action, url2, message, resolve, reject));
639
1259
  }
640
1260
  if (mode === "redirect") {
641
1261
  const url2 = `${this.host}/widget/${action}?origin=${origin}${eoaFlag}`;
@@ -647,10 +1267,14 @@ var OpenKey = class {
647
1267
  return this.openIframeModal(url, action, message, origin);
648
1268
  }
649
1269
  openIframeModal(url, action, message, origin) {
1270
+ const outgoingRequestId = message.requestId;
1271
+ const outgoingProtocolVersion = message.protocolVersion;
1272
+ const isVersionedRequest = typeof outgoingRequestId === "string" && typeof outgoingProtocolVersion === "number" && outgoingProtocolVersion >= 1;
650
1273
  return new Promise((resolve, reject) => {
651
1274
  let readyReceived = false;
652
1275
  let settled = false;
653
1276
  let modal = null;
1277
+ let cancel;
654
1278
  const settle = (fn) => {
655
1279
  if (settled) return;
656
1280
  settled = true;
@@ -661,15 +1285,14 @@ var OpenKey = class {
661
1285
  clearTimeout(overallTimeout);
662
1286
  modal?.destroy();
663
1287
  modal = null;
1288
+ this.activeFlowCancellations.delete(cancel);
664
1289
  };
665
1290
  modal = new IframeModal({
666
1291
  url,
667
1292
  host: this.host,
668
1293
  onClose: () => {
669
1294
  settle(() => {
670
- clearTimeout(readyTimeout);
671
- clearTimeout(overallTimeout);
672
- modal = null;
1295
+ cleanup();
673
1296
  reject({ code: "USER_CANCELLED", message: "User cancelled the request" });
674
1297
  });
675
1298
  },
@@ -679,6 +1302,12 @@ var OpenKey = class {
679
1302
  modal?.postMessage(message);
680
1303
  return;
681
1304
  }
1305
+ if (isVersionedRequest) {
1306
+ const respRequestId = data.requestId;
1307
+ const respVersion = data.protocolVersion;
1308
+ if (respRequestId !== outgoingRequestId) return;
1309
+ if (respVersion !== outgoingProtocolVersion) return;
1310
+ }
682
1311
  if (data.type === "openkey:close") {
683
1312
  settle(() => {
684
1313
  cleanup();
@@ -695,7 +1324,7 @@ var OpenKey = class {
695
1324
  this.handleExternalWalletConnect(resolve, reject);
696
1325
  return;
697
1326
  }
698
- if (data.type === "openkey:auth:response" || data.type === "openkey:sign:response" || data.type === "openkey:signTypedData:response" || data.type === "openkey:link-wallet:response") {
1327
+ if (this.isResponseForAction(action, data)) {
699
1328
  settle(() => {
700
1329
  cleanup();
701
1330
  if (data.success) {
@@ -704,8 +1333,24 @@ var OpenKey = class {
704
1333
  resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || "MANAGED" });
705
1334
  } else if (data.type === "openkey:link-wallet:response") {
706
1335
  resolve({ address: data.address, keyId: data.keyId });
1336
+ } else if (data.type === "openkey:sign-out:response") {
1337
+ resolve({ requestId: data.requestId, revoked: data.revoked });
1338
+ } else if (data.type === "openkey:externalSign:approve") {
1339
+ resolve({
1340
+ authorizationContextToken: data.authorizationContextToken,
1341
+ previewApprovalToken: data.previewApprovalToken,
1342
+ signedMessage: data.signedMessage,
1343
+ selectedActionIds: data.selectedActionIds,
1344
+ address: data.address
1345
+ });
707
1346
  } else {
708
- resolve({ signature: data.signature, address: data.address });
1347
+ resolve({
1348
+ signature: data.signature,
1349
+ address: data.address,
1350
+ signedMessage: data.signedMessage,
1351
+ selectedActionKeys: data.selectedActionKeys,
1352
+ permissions: data.permissions
1353
+ });
709
1354
  }
710
1355
  } else {
711
1356
  reject(data.error);
@@ -714,13 +1359,19 @@ var OpenKey = class {
714
1359
  }
715
1360
  }
716
1361
  });
1362
+ if (isVersionedRequest) {
1363
+ modal.setExpectedCorrelation(
1364
+ outgoingRequestId,
1365
+ outgoingProtocolVersion
1366
+ );
1367
+ }
717
1368
  const readyTimeout = setTimeout(() => {
718
1369
  if (readyReceived || settled) return;
719
1370
  cleanup();
720
1371
  console.warn("OpenKey: iframe blocked by CSP, falling back to popup. Add frame-src https://openkey.so to your CSP.");
721
1372
  showToast();
722
1373
  const popupUrl = `${this.host}/widget/${action}?origin=${origin}`;
723
- this.openPopup(popupUrl, message, (val) => settle(() => resolve(val)), (err) => settle(() => reject(err)));
1374
+ this.openPopup(action, popupUrl, message, (val) => settle(() => resolve(val)), (err) => settle(() => reject(err)));
724
1375
  }, IFRAME_READY_TIMEOUT);
725
1376
  const overallTimeout = setTimeout(() => {
726
1377
  settle(() => {
@@ -728,8 +1379,24 @@ var OpenKey = class {
728
1379
  reject({ code: "TIMEOUT", message: "Request timed out" });
729
1380
  });
730
1381
  }, DEFAULT_TIMEOUT);
1382
+ cancel = () => {
1383
+ settle(() => {
1384
+ cleanup();
1385
+ reject({ code: "USER_CANCELLED", message: "Superseded by sign-out" });
1386
+ });
1387
+ };
1388
+ this.activeFlowCancellations.add(cancel);
731
1389
  });
732
1390
  }
1391
+ isResponseForAction(action, data) {
1392
+ const type = data.type;
1393
+ if (action === "connect") return type === "openkey:auth:response";
1394
+ if (action === "sign") return type === "openkey:sign:response" || type === "openkey:externalSign:approve";
1395
+ if (action === "sign-typed-data") return type === "openkey:signTypedData:response";
1396
+ if (action === "link-wallet") return type === "openkey:link-wallet:response";
1397
+ if (action === "sign-out") return type === "openkey:sign-out:response";
1398
+ return false;
1399
+ }
733
1400
  handleWalletLinkDelegation(modal) {
734
1401
  const sendResult = (result) => modal.postMessage(result);
735
1402
  new WalletPicker({
@@ -791,15 +1458,16 @@ var OpenKey = class {
791
1458
  }
792
1459
  });
793
1460
  }
794
- openPopup(url, message, resolve, reject) {
1461
+ openPopup(action, url, message, resolve, reject) {
795
1462
  const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
796
1463
  const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;
797
- this.popup = window.open(
1464
+ const popup = window.open(
798
1465
  url,
799
- "openkey",
1466
+ `openkey-${action}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
800
1467
  `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
801
1468
  );
802
- if (!this.popup) {
1469
+ this.popup = popup;
1470
+ if (!popup) {
803
1471
  showToast("Popup was blocked. Please allow popups for this site.", "error");
804
1472
  reject({
805
1473
  code: "POPUP_BLOCKED",
@@ -807,74 +1475,129 @@ var OpenKey = class {
807
1475
  });
808
1476
  return;
809
1477
  }
1478
+ let settled = false;
1479
+ let cancel;
810
1480
  const cleanup = () => {
811
1481
  window.removeEventListener("message", handleMessage);
812
1482
  clearInterval(pollClosed);
813
1483
  clearTimeout(timeout);
1484
+ this.activeFlowCancellations.delete(cancel);
1485
+ if (this.popup === popup) this.popup = null;
1486
+ };
1487
+ const settle = (fn) => {
1488
+ if (settled) return;
1489
+ settled = true;
1490
+ cleanup();
1491
+ fn();
814
1492
  };
815
1493
  const pollClosed = setInterval(() => {
816
- if (this.popup?.closed) {
817
- cleanup();
818
- reject({
1494
+ if (popup.closed) {
1495
+ settle(() => reject({
819
1496
  code: "USER_CANCELLED",
820
1497
  message: "User closed the popup"
821
- });
1498
+ }));
822
1499
  }
823
1500
  }, 500);
824
1501
  const timeout = setTimeout(() => {
825
- cleanup();
826
- this.popup?.close();
827
- reject({
828
- code: "TIMEOUT",
829
- message: "Request timed out"
1502
+ settle(() => {
1503
+ popup.close();
1504
+ reject({
1505
+ code: "TIMEOUT",
1506
+ message: "Request timed out"
1507
+ });
830
1508
  });
831
1509
  }, DEFAULT_TIMEOUT);
1510
+ const outgoingRequestId = message.requestId;
1511
+ const outgoingProtocolVersion = message.protocolVersion;
1512
+ const isVersionedRequest = typeof outgoingRequestId === "string" && typeof outgoingProtocolVersion === "number" && outgoingProtocolVersion >= 1;
832
1513
  const handleMessage = (event) => {
833
1514
  if (event.origin !== this.host) return;
834
- if (event.source !== this.popup) return;
1515
+ if (event.source !== popup) return;
835
1516
  const data = event.data;
836
1517
  if (data.type === "openkey:ready") {
837
- this.popup?.postMessage(message, this.host);
1518
+ popup.postMessage(message, this.host);
838
1519
  return;
839
1520
  }
840
1521
  if (data.type === "openkey:close") {
841
- cleanup();
842
- this.popup?.close();
843
- reject({
844
- code: "USER_CANCELLED",
845
- message: "User cancelled the request"
1522
+ if (isVersionedRequest) {
1523
+ const closeRequestId = data.requestId;
1524
+ const closeVersion = data.protocolVersion;
1525
+ if (closeRequestId !== outgoingRequestId) return;
1526
+ if (closeVersion !== outgoingProtocolVersion) return;
1527
+ }
1528
+ settle(() => {
1529
+ popup.close();
1530
+ reject({
1531
+ code: "USER_CANCELLED",
1532
+ message: "User cancelled the request"
1533
+ });
846
1534
  });
847
1535
  return;
848
1536
  }
849
1537
  if (data.type === "openkey:auth:use-external-wallet") {
850
- cleanup();
851
- this.popup?.close();
852
- this.handleExternalWalletConnect(resolve, reject);
1538
+ settle(() => {
1539
+ popup.close();
1540
+ this.handleExternalWalletConnect(resolve, reject);
1541
+ });
853
1542
  return;
854
1543
  }
855
- if (data.type === "openkey:auth:response" || data.type === "openkey:sign:response" || data.type === "openkey:signTypedData:response" || data.type === "openkey:link-wallet:response") {
856
- cleanup();
857
- this.popup?.close();
858
- if (data.success) {
859
- if (data.type === "openkey:auth:response") {
860
- if (data.sessionToken) this.sessionToken = data.sessionToken;
861
- resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || "MANAGED" });
862
- } else if (data.type === "openkey:link-wallet:response") {
863
- resolve({ address: data.address, keyId: data.keyId });
1544
+ if (this.isResponseForAction(action, data)) {
1545
+ if (isVersionedRequest) {
1546
+ const respRequestId = data.requestId;
1547
+ const respVersion = data.protocolVersion;
1548
+ if (respRequestId !== outgoingRequestId) return;
1549
+ if (respVersion !== outgoingProtocolVersion) return;
1550
+ }
1551
+ settle(() => {
1552
+ popup.close();
1553
+ if (data.success) {
1554
+ if (data.type === "openkey:auth:response") {
1555
+ if (data.sessionToken) this.sessionToken = data.sessionToken;
1556
+ resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || "MANAGED" });
1557
+ } else if (data.type === "openkey:link-wallet:response") {
1558
+ resolve({ address: data.address, keyId: data.keyId });
1559
+ } else if (data.type === "openkey:sign-out:response") {
1560
+ resolve({ requestId: data.requestId, revoked: data.revoked });
1561
+ } else if (data.type === "openkey:externalSign:approve") {
1562
+ resolve({
1563
+ authorizationContextToken: data.authorizationContextToken,
1564
+ previewApprovalToken: data.previewApprovalToken,
1565
+ signedMessage: data.signedMessage,
1566
+ selectedActionIds: data.selectedActionIds,
1567
+ address: data.address
1568
+ });
1569
+ } else {
1570
+ resolve({
1571
+ signature: data.signature,
1572
+ address: data.address,
1573
+ signedMessage: data.signedMessage,
1574
+ selectedActionKeys: data.selectedActionKeys,
1575
+ permissions: data.permissions
1576
+ });
1577
+ }
864
1578
  } else {
865
- resolve({ signature: data.signature, address: data.address });
1579
+ reject(data.error);
866
1580
  }
867
- } else {
868
- reject(data.error);
869
- }
1581
+ });
870
1582
  }
871
1583
  };
872
1584
  window.addEventListener("message", handleMessage);
1585
+ cancel = () => {
1586
+ settle(() => {
1587
+ popup.close();
1588
+ reject({ code: "USER_CANCELLED", message: "Superseded by sign-out" });
1589
+ });
1590
+ };
1591
+ this.activeFlowCancellations.add(cancel);
1592
+ }
1593
+ cancelActiveFlows() {
1594
+ for (const cancel of [...this.activeFlowCancellations]) cancel();
873
1595
  }
874
1596
  /**
875
1597
  * Disconnect and close any open flows
876
1598
  */
877
1599
  disconnect() {
1600
+ this.cancelActiveFlows();
878
1601
  this.popup?.close();
879
1602
  this.popup = null;
880
1603
  }
@@ -947,8 +1670,13 @@ function getOpenKey(config) {
947
1670
  }
948
1671
  // Annotate the CommonJS export names for ESM import in node:
949
1672
  0 && (module.exports = {
1673
+ KeyIdTypeMismatchError,
950
1674
  OpenKey,
1675
+ OpenKeyNostr,
951
1676
  OpenKeyProvider,
952
- getOpenKey
1677
+ getOpenKey,
1678
+ resolveAuthorizeTinyCloudRouting,
1679
+ shouldRouteAuthorizeTinyCloudExternal,
1680
+ validateIframeResize
953
1681
  });
954
1682
  //# sourceMappingURL=index.js.map