@openkey/sdk 0.9.0 → 0.10.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/dist/index.js CHANGED
@@ -20,19 +20,332 @@ 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) return null;
277
+ if (typeof data.protocolVersion !== "number" || data.protocolVersion !== expected.protocolVersion) {
278
+ return null;
279
+ }
280
+ if (typeof data.requestId !== "string" || data.requestId !== expected.requestId) {
281
+ return null;
282
+ }
283
+ if (typeof data.height !== "number" || !Number.isFinite(data.height) || data.height <= 0) {
284
+ return null;
285
+ }
286
+ return Math.min(data.height, Math.floor(expected.viewportHeight * 0.85));
287
+ }
288
+ function shouldRouteAuthorizeTinyCloudExternal(state) {
289
+ return state.lastAuth?.keyType === "EXTERNAL";
290
+ }
291
+ var KeyIdTypeMismatchError = class extends Error {
292
+ code;
293
+ requestKeyId;
294
+ lastAuthKeyId;
295
+ requestKeyType;
296
+ lastAuthKeyType;
297
+ constructor(fields) {
298
+ super(fields.message);
299
+ this.name = "KeyIdTypeMismatchError";
300
+ this.code = "KEY_ID_TYPE_MISMATCH";
301
+ this.requestKeyId = fields.requestKeyId;
302
+ this.lastAuthKeyId = fields.lastAuthKeyId;
303
+ this.requestKeyType = fields.requestKeyType;
304
+ this.lastAuthKeyType = fields.lastAuthKeyType;
305
+ }
306
+ };
307
+ function resolveAuthorizeTinyCloudRouting(state) {
308
+ const lastAuthKeyId = state.lastAuth?.keyId ?? null;
309
+ const lastAuthKeyType = state.lastAuth?.keyType ?? null;
310
+ const rawRequestKeyId = state.requestKeyId ?? null;
311
+ const requestKeyId = rawRequestKeyId && rawRequestKeyId.length > 0 ? rawRequestKeyId : null;
312
+ const knownKeys = state.knownKeys ?? [];
313
+ const lookupType = (id) => {
314
+ for (const k2 of knownKeys) {
315
+ if (k2.id === id) return k2.keyType;
316
+ if (k2.address && k2.address.toLowerCase() === id.toLowerCase()) return k2.keyType;
317
+ }
318
+ if (id.startsWith("external:")) return "EXTERNAL";
319
+ return null;
320
+ };
321
+ if (!requestKeyId) {
322
+ const route = lastAuthKeyType === "EXTERNAL" ? "external" : "managed";
323
+ return {
324
+ route,
325
+ resolvedKeyId: lastAuthKeyId,
326
+ resolvedKeyType: lastAuthKeyType
327
+ };
328
+ }
329
+ let requestKeyType = lookupType(requestKeyId);
330
+ if (!requestKeyType && lastAuthKeyId && requestKeyId === lastAuthKeyId) {
331
+ requestKeyType = lastAuthKeyType;
332
+ }
333
+ if (requestKeyType && lastAuthKeyType && requestKeyType !== lastAuthKeyType) {
334
+ throw new KeyIdTypeMismatchError({
335
+ message: `authorizeTinyCloud: request.keyId ${requestKeyId} resolves to ${requestKeyType} but active session key ${lastAuthKeyId} is ${lastAuthKeyType}; refuse to route silently`,
336
+ requestKeyId,
337
+ lastAuthKeyId,
338
+ requestKeyType,
339
+ lastAuthKeyType
340
+ });
341
+ }
342
+ const effectiveType = requestKeyType ?? lastAuthKeyType;
343
+ return {
344
+ route: effectiveType === "EXTERNAL" ? "external" : "managed",
345
+ resolvedKeyId: requestKeyId,
346
+ resolvedKeyType: effectiveType
347
+ };
348
+ }
36
349
  var IframeModal = class {
37
350
  root;
38
351
  shadow;
@@ -41,6 +354,13 @@ var IframeModal = class {
41
354
  onMessage;
42
355
  messageHandler;
43
356
  host;
357
+ // Sol final continuation contract requirement 5: iframe resize traffic
358
+ // MUST correlate to the active request's requestId AND protocolVersion.
359
+ // Set by `setExpectedCorrelation` after the outer flow decides which
360
+ // request this modal is bound to. When both are null, resize messages
361
+ // are dropped (no active request → no resize authority).
362
+ expectedRequestId = null;
363
+ expectedProtocolVersion = null;
44
364
  constructor(opts) {
45
365
  this.host = opts.host;
46
366
  this.onClose = opts.onClose;
@@ -96,10 +416,17 @@ var IframeModal = class {
96
416
  if (event.source !== this.iframe.contentWindow) return;
97
417
  const data = event.data;
98
418
  if (data.type === "openkey:resize") {
99
- const h = Math.min(data.height, Math.floor(window.innerHeight * 0.85));
419
+ const h = validateIframeResize(event.data, {
420
+ requestId: this.expectedRequestId,
421
+ protocolVersion: this.expectedProtocolVersion,
422
+ viewportHeight: window.innerHeight
423
+ });
424
+ if (h === null) return;
100
425
  this.iframe.style.height = `${h}px`;
101
426
  return;
102
427
  }
428
+ if (data.type === "openkey:close") {
429
+ }
103
430
  this.onMessage(data);
104
431
  };
105
432
  window.addEventListener("message", this.messageHandler);
@@ -108,6 +435,20 @@ var IframeModal = class {
108
435
  postMessage(message) {
109
436
  this.iframe.contentWindow?.postMessage(message, this.host);
110
437
  }
438
+ /**
439
+ * Sol final continuation contract requirement 5: bind the active
440
+ * request correlation so subsequent resize (and future correlated)
441
+ * messages can be verified against `requestId` + `protocolVersion`.
442
+ *
443
+ * The outer flow calls this immediately BEFORE posting the sign
444
+ * request into the iframe, so a resize that arrives synchronously
445
+ * after ready is correlated correctly. Passing `null` for either
446
+ * argument disables correlation (resize is dropped).
447
+ */
448
+ setExpectedCorrelation(requestId, protocolVersion) {
449
+ this.expectedRequestId = requestId;
450
+ this.expectedProtocolVersion = protocolVersion;
451
+ }
111
452
  destroy() {
112
453
  window.removeEventListener("message", this.messageHandler);
113
454
  this.root.remove();
@@ -241,12 +582,23 @@ var OpenKey = class {
241
582
  lastAuth = null;
242
583
  discoveredProviders = [];
243
584
  sessionToken = null;
585
+ /** Cancels widget flows that could outlive a sign-out on this instance. */
586
+ activeFlowCancellations = /* @__PURE__ */ new Set();
587
+ /**
588
+ * Nostr identity custody + signing (secp256k1 Schnorr / BIP-340). Kept
589
+ * fully separate from the Ethereum flows above: it never reuses
590
+ * `lastAuth`/`sessionToken`, and its widget messages never carry a
591
+ * sessionToken - the OpenKey session and any signing grants stay inside
592
+ * the OpenKey-origin iframe for the whole lifetime of this client.
593
+ */
594
+ nostr;
244
595
  constructor(config = {}) {
245
596
  this.config = config;
246
597
  this.host = config.host || DEFAULT_HOST;
247
598
  this.oauthHost = config.oauthHost || this.deriveOAuthHost(this.host);
248
599
  this.appName = config.appName || window.location.hostname;
249
600
  this.mode = config.mode ?? "iframe";
601
+ this.nostr = new OpenKeyNostr(this.host);
250
602
  if (typeof window !== "undefined") {
251
603
  this.discoveredProviders = [];
252
604
  window.addEventListener("eip6963:announceProvider", (event) => {
@@ -272,6 +624,37 @@ var OpenKey = class {
272
624
  return "https://api.openkey.so";
273
625
  }
274
626
  }
627
+ /**
628
+ * Sol MAJOR-4 (final continuation): fetch the caller's registered keys
629
+ * so `resolveAuthorizeTinyCloudRouting` can resolve an explicit pin
630
+ * whose keyId does not match the active session key. Called only when
631
+ * the caller passes a `requestKeyId` we do not already recognise; the
632
+ * common no-pin path skips the network round-trip.
633
+ *
634
+ * Failures do NOT block routing — we fall through with `knownKeys=[]`
635
+ * so the resolver's lastAuth-based fallback still runs. If the caller
636
+ * pinned a genuinely unknown key that turns out to be external, the
637
+ * widget rejects the server-signing path and the SDK surfaces that
638
+ * error from the finalize call rather than silently routing wrong.
639
+ */
640
+ async fetchKnownKeysIfNeeded(requestKeyId) {
641
+ if (!requestKeyId) return [];
642
+ const la = this.lastAuth;
643
+ if (la?.keyId === requestKeyId) return [];
644
+ if (la?.address && la.address.toLowerCase() === requestKeyId.toLowerCase()) return [];
645
+ if (requestKeyId.startsWith("external:")) return [];
646
+ try {
647
+ const res = await fetch(`${this.oauthHost}/api/keys`, {
648
+ method: "GET",
649
+ credentials: "include"
650
+ });
651
+ if (!res.ok) return [];
652
+ const body = await res.json();
653
+ return Array.isArray(body?.keys) ? body.keys : [];
654
+ } catch {
655
+ return [];
656
+ }
657
+ }
275
658
  /**
276
659
  * Connect to OpenKey and get user's wallet address
277
660
  * Opens auth flow for user to select/create a key
@@ -284,6 +667,36 @@ var OpenKey = class {
284
667
  this.lastAuth = result;
285
668
  return result;
286
669
  }
670
+ /**
671
+ * Signs the active OpenKey account out through the OpenKey-owned widget.
672
+ *
673
+ * The SDK passes its Better Auth bearer through the OpenKey-owned widget
674
+ * protocol so applications do not have to implement revocation themselves.
675
+ * Local SDK session state is cleared before opening the widget, including
676
+ * when remote revocation is unavailable.
677
+ */
678
+ async signOut(opts) {
679
+ const requestId = `ok-signout-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
680
+ const sessionToken = this.sessionToken || void 0;
681
+ const mode = this.resolveMode(opts?.mode) === "redirect" ? "popup" : opts?.mode;
682
+ this.cancelActiveFlows();
683
+ this.lastAuth = null;
684
+ this.sessionToken = null;
685
+ const acknowledgement = await this.openFlow(
686
+ "sign-out",
687
+ {
688
+ type: "openkey:sign-out:request",
689
+ requestId,
690
+ protocolVersion: 1,
691
+ sessionToken
692
+ },
693
+ mode
694
+ );
695
+ if (acknowledgement.requestId !== requestId) {
696
+ throw new Error("OpenKey signOut received an acknowledgement for a different request");
697
+ }
698
+ return acknowledgement;
699
+ }
287
700
  /**
288
701
  * Better-auth session token relayed during connect(), or null if the
289
702
  * flow did not provide one. Use as a bearer token for API calls made
@@ -322,6 +735,208 @@ var OpenKey = class {
322
735
  }
323
736
  return this.signWithOpenKey(request, opts?.mode);
324
737
  }
738
+ /**
739
+ * Versioned TinyCloud authorization protocol (v1).
740
+ *
741
+ * Unlike `signMessage()` (which is contractually byte-exact — the returned
742
+ * signature always verifies against the caller's exact bytes), this method
743
+ * permits the user to narrow the requested capabilities before signing.
744
+ * OpenKey may therefore regenerate the SIWE. The response carries
745
+ * `signedMessage` — the exact bytes the signature actually verifies against.
746
+ *
747
+ * TinyCloud completes the session with `signedMessage`, not the original
748
+ * `siwe` passed in. Consumers MUST use the returned bytes and MUST NOT
749
+ * assume the returned grants are a superset of what they requested — the
750
+ * user is allowed to remove any grant that is not required.
751
+ *
752
+ * Legacy `signMessage()` callers are unaffected: their exact-byte contract
753
+ * remains preserved and OpenKey never rewrites their bytes.
754
+ */
755
+ async authorizeTinyCloud(request, opts) {
756
+ if (request.protocolVersion !== 1) {
757
+ throw new Error(
758
+ `authorizeTinyCloud only supports protocolVersion 1; got ${request.protocolVersion}`
759
+ );
760
+ }
761
+ if (typeof request.siwe !== "string" || !request.siwe) {
762
+ throw new Error("authorizeTinyCloud requires a non-empty SIWE");
763
+ }
764
+ let routing;
765
+ try {
766
+ const knownKeys = await this.fetchKnownKeysIfNeeded(request.keyId);
767
+ routing = resolveAuthorizeTinyCloudRouting({
768
+ lastAuth: this.lastAuth,
769
+ requestKeyId: request.keyId,
770
+ knownKeys
771
+ });
772
+ } catch (e) {
773
+ if (e instanceof KeyIdTypeMismatchError) throw e;
774
+ throw e instanceof Error ? e : new Error("authorizeTinyCloud: failed to resolve target key type");
775
+ }
776
+ if (routing.route === "external") {
777
+ return this.authorizeTinyCloudExternal(request, opts);
778
+ }
779
+ const requestId = `ok-auth-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
780
+ const resolvedKeyId = routing.resolvedKeyId ?? request.keyId ?? this.lastAuth?.keyId;
781
+ const raw = await this.openFlow(
782
+ "sign",
783
+ {
784
+ type: "openkey:sign:request",
785
+ requestId,
786
+ protocolVersion: 1,
787
+ message: request.siwe,
788
+ keyId: resolvedKeyId,
789
+ // Forward JWK so the widget can pass it to /authorize-sign for
790
+ // narrowed-SIWE regeneration.
791
+ jwk: request.jwk,
792
+ // Forward the TinyCloud host so the widget can bind it into the
793
+ // /authorize-sign-prepare context (Sol MAJOR-2).
794
+ host: request.host,
795
+ // Sol MAJOR-2 (envelope): forward the caller-supplied presentation
796
+ // envelope so the widget can render honest provenance and the
797
+ // /authorize-sign-prepare route can bind it into the authorization
798
+ // context. The envelope is display-only; the widget transport
799
+ // validates and size-bounds it before use and NEVER treats it as
800
+ // verified unless the server actually origin-binds a manifest.
801
+ presentation: request.presentation,
802
+ sessionToken: this.sessionToken || void 0
803
+ },
804
+ opts?.mode
805
+ );
806
+ if (typeof raw.signedMessage !== "string" || !raw.signedMessage) {
807
+ throw new Error(
808
+ "authorizeTinyCloud: widget returned no signedMessage \u2014 signature would not correspond to the displayed authorization"
809
+ );
810
+ }
811
+ if (!Array.isArray(raw.selectedActionKeys)) {
812
+ throw new Error(
813
+ "authorizeTinyCloud: widget returned no selectedActionKeys \u2014 cannot verify displayed authorization matches the signed bytes"
814
+ );
815
+ }
816
+ if (!Array.isArray(raw.permissions)) {
817
+ throw new Error(
818
+ "authorizeTinyCloud: widget returned no permissions \u2014 cannot verify displayed authorization matches the signed bytes"
819
+ );
820
+ }
821
+ return {
822
+ protocolVersion: 1,
823
+ address: raw.address,
824
+ signature: raw.signature,
825
+ signedMessage: raw.signedMessage,
826
+ selectedActionKeys: raw.selectedActionKeys,
827
+ permissions: raw.permissions
828
+ };
829
+ }
830
+ /**
831
+ * Sol MAJOR-3 (continuation): authorizeTinyCloud path for external keys.
832
+ * The wallet lives outside OpenKey, so the SDK opens the shared widget
833
+ * for review + preview, then invokes the user's wallet to sign the
834
+ * preview bytes, then finalizes server-side via /authorize-sign.
835
+ *
836
+ * Flow:
837
+ * 1. Open the widget with `externalSign: true` — the widget renders
838
+ * the shared SigningApproval UI, calls /authorize-sign-prepare,
839
+ * calls /authorize-sign-preview after the user approves a
840
+ * selection, and hands back `{ previewApprovalToken,
841
+ * signedMessage, selectedActionIds, address, authorizationContextToken }`.
842
+ * 2. SDK invokes the user's wallet on the returned signedMessage.
843
+ * 3. SDK POSTs /authorize-sign with `externalSignature`.
844
+ *
845
+ * Legacy exact-byte signMessage callers are unaffected — this path is
846
+ * only taken via authorizeTinyCloud (protocolVersion:1) when the target
847
+ * key is EXTERNAL. Sol's rejection required this path to go through
848
+ * the same review UI as managed keys.
849
+ */
850
+ async authorizeTinyCloudExternal(request, opts) {
851
+ const keyId = request.keyId ?? this.lastAuth?.keyId;
852
+ if (!keyId) {
853
+ throw new Error(
854
+ "authorizeTinyCloud (external) requires a keyId \u2014 call connect() first or pass request.keyId"
855
+ );
856
+ }
857
+ const walletAddress = this.lastAuth?.address;
858
+ if (!walletAddress) {
859
+ throw new Error(
860
+ "authorizeTinyCloud (external) requires a connected wallet \u2014 call connect() first"
861
+ );
862
+ }
863
+ const requestId = `ok-ext-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
864
+ const approval = await this.openFlow(
865
+ "sign",
866
+ {
867
+ type: "openkey:sign:request",
868
+ requestId,
869
+ protocolVersion: 1,
870
+ message: request.siwe,
871
+ keyId,
872
+ jwk: request.jwk,
873
+ host: request.host,
874
+ // Sol MAJOR-2 (envelope): forward the caller-supplied presentation
875
+ // envelope. Same rules as the managed path — display-only, size-
876
+ // bounded at the transport, never treated as verified without
877
+ // server-side origin-binding.
878
+ presentation: request.presentation,
879
+ sessionToken: this.sessionToken || void 0,
880
+ // Sol MAJOR-3 (continuation): tell the widget to hand us back
881
+ // the previewApproval instead of asking OpenKey to sign
882
+ // server-side with a managed key. The widget will still render
883
+ // the SAME SigningApproval component; it just diverges at the
884
+ // "sign" step and emits `openkey:externalSign:approve` back
885
+ // through the transport.
886
+ externalSign: true
887
+ },
888
+ opts?.mode
889
+ );
890
+ if (!approval?.previewApprovalToken || !approval?.signedMessage) {
891
+ throw new Error(
892
+ "authorizeTinyCloud (external): widget did not return a previewApprovalToken + signedMessage"
893
+ );
894
+ }
895
+ if (approval.address && approval.address.toLowerCase() !== walletAddress.toLowerCase()) {
896
+ throw new Error(
897
+ `authorizeTinyCloud (external): widget preview address ${approval.address} does not match connected wallet ${walletAddress}`
898
+ );
899
+ }
900
+ const provider = await this.findWalletProvider(walletAddress);
901
+ const hexMessage = this.toHex(approval.signedMessage);
902
+ const walletSignature = await provider.request({
903
+ method: "personal_sign",
904
+ params: [hexMessage, walletAddress]
905
+ });
906
+ const finalizeRes = await fetch(
907
+ `${this.oauthHost}/api/delegate/authorize-sign`,
908
+ {
909
+ method: "POST",
910
+ credentials: "include",
911
+ headers: { "Content-Type": "application/json" },
912
+ body: JSON.stringify({
913
+ authorizationContextToken: approval.authorizationContextToken,
914
+ previewApprovalToken: approval.previewApprovalToken,
915
+ selectedActionIds: approval.selectedActionIds,
916
+ protocolVersion: 1,
917
+ externalSignature: walletSignature
918
+ })
919
+ }
920
+ );
921
+ if (!finalizeRes.ok) {
922
+ const body = await finalizeRes.json().catch(() => ({ error: `HTTP ${finalizeRes.status}` }));
923
+ throw new Error(body.error || `authorize-sign failed (${finalizeRes.status})`);
924
+ }
925
+ const finalize = await finalizeRes.json();
926
+ if (finalize.signedMessage !== approval.signedMessage) {
927
+ throw new Error(
928
+ "authorizeTinyCloud (external): server signed different bytes than the preview showed"
929
+ );
930
+ }
931
+ return {
932
+ protocolVersion: 1,
933
+ address: finalize.address,
934
+ signature: finalize.signature,
935
+ signedMessage: finalize.signedMessage,
936
+ selectedActionKeys: finalize.selectedActionKeys ?? [],
937
+ permissions: finalize.permissions ?? []
938
+ };
939
+ }
325
940
  /**
326
941
  * Sign typed data (EIP-712) with the user's OpenKey wallet
327
942
  * For external keys, routes directly to the user's wallet provider
@@ -456,14 +1071,14 @@ var OpenKey = class {
456
1071
  * @returns Promise that resolves with authorization code after user consent
457
1072
  */
458
1073
  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)();
1074
+ const verifier = y();
1075
+ const challenge = await k(verifier);
1076
+ const state = config.state || m();
462
1077
  sessionStorage.setItem(
463
1078
  OAUTH_STORAGE_KEY,
464
1079
  JSON.stringify({ verifier, state })
465
1080
  );
466
- const authUrl = (0, import_core.buildAuthorizationUrl)({
1081
+ const authUrl = T({
467
1082
  host: this.oauthHost,
468
1083
  clientId: config.clientId,
469
1084
  redirectUri: config.redirectUri,
@@ -635,7 +1250,7 @@ var OpenKey = class {
635
1250
  const eoaFlag = action === "connect" && this.hasDetectedEoa() ? "&hasEoa=true" : "";
636
1251
  if (mode === "popup") {
637
1252
  const url2 = `${this.host}/widget/${action}?origin=${origin}${eoaFlag}`;
638
- return new Promise((resolve, reject) => this.openPopup(url2, message, resolve, reject));
1253
+ return new Promise((resolve, reject) => this.openPopup(action, url2, message, resolve, reject));
639
1254
  }
640
1255
  if (mode === "redirect") {
641
1256
  const url2 = `${this.host}/widget/${action}?origin=${origin}${eoaFlag}`;
@@ -647,10 +1262,14 @@ var OpenKey = class {
647
1262
  return this.openIframeModal(url, action, message, origin);
648
1263
  }
649
1264
  openIframeModal(url, action, message, origin) {
1265
+ const outgoingRequestId = message.requestId;
1266
+ const outgoingProtocolVersion = message.protocolVersion;
1267
+ const isVersionedRequest = typeof outgoingRequestId === "string" && typeof outgoingProtocolVersion === "number" && outgoingProtocolVersion >= 1;
650
1268
  return new Promise((resolve, reject) => {
651
1269
  let readyReceived = false;
652
1270
  let settled = false;
653
1271
  let modal = null;
1272
+ let cancel;
654
1273
  const settle = (fn) => {
655
1274
  if (settled) return;
656
1275
  settled = true;
@@ -661,24 +1280,35 @@ var OpenKey = class {
661
1280
  clearTimeout(overallTimeout);
662
1281
  modal?.destroy();
663
1282
  modal = null;
1283
+ this.activeFlowCancellations.delete(cancel);
664
1284
  };
665
1285
  modal = new IframeModal({
666
1286
  url,
667
1287
  host: this.host,
668
1288
  onClose: () => {
669
1289
  settle(() => {
670
- clearTimeout(readyTimeout);
671
- clearTimeout(overallTimeout);
672
- modal = null;
1290
+ cleanup();
673
1291
  reject({ code: "USER_CANCELLED", message: "User cancelled the request" });
674
1292
  });
675
1293
  },
676
1294
  onMessage: (data) => {
677
1295
  if (data.type === "openkey:ready") {
678
1296
  readyReceived = true;
1297
+ if (isVersionedRequest && modal) {
1298
+ modal.setExpectedCorrelation(
1299
+ outgoingRequestId,
1300
+ outgoingProtocolVersion
1301
+ );
1302
+ }
679
1303
  modal?.postMessage(message);
680
1304
  return;
681
1305
  }
1306
+ if (isVersionedRequest) {
1307
+ const respRequestId = data.requestId;
1308
+ const respVersion = data.protocolVersion;
1309
+ if (respRequestId !== outgoingRequestId) return;
1310
+ if (respVersion !== outgoingProtocolVersion) return;
1311
+ }
682
1312
  if (data.type === "openkey:close") {
683
1313
  settle(() => {
684
1314
  cleanup();
@@ -695,7 +1325,7 @@ var OpenKey = class {
695
1325
  this.handleExternalWalletConnect(resolve, reject);
696
1326
  return;
697
1327
  }
698
- if (data.type === "openkey:auth:response" || data.type === "openkey:sign:response" || data.type === "openkey:signTypedData:response" || data.type === "openkey:link-wallet:response") {
1328
+ if (this.isResponseForAction(action, data)) {
699
1329
  settle(() => {
700
1330
  cleanup();
701
1331
  if (data.success) {
@@ -704,8 +1334,24 @@ var OpenKey = class {
704
1334
  resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || "MANAGED" });
705
1335
  } else if (data.type === "openkey:link-wallet:response") {
706
1336
  resolve({ address: data.address, keyId: data.keyId });
1337
+ } else if (data.type === "openkey:sign-out:response") {
1338
+ resolve({ requestId: data.requestId, revoked: data.revoked });
1339
+ } else if (data.type === "openkey:externalSign:approve") {
1340
+ resolve({
1341
+ authorizationContextToken: data.authorizationContextToken,
1342
+ previewApprovalToken: data.previewApprovalToken,
1343
+ signedMessage: data.signedMessage,
1344
+ selectedActionIds: data.selectedActionIds,
1345
+ address: data.address
1346
+ });
707
1347
  } else {
708
- resolve({ signature: data.signature, address: data.address });
1348
+ resolve({
1349
+ signature: data.signature,
1350
+ address: data.address,
1351
+ signedMessage: data.signedMessage,
1352
+ selectedActionKeys: data.selectedActionKeys,
1353
+ permissions: data.permissions
1354
+ });
709
1355
  }
710
1356
  } else {
711
1357
  reject(data.error);
@@ -720,7 +1366,7 @@ var OpenKey = class {
720
1366
  console.warn("OpenKey: iframe blocked by CSP, falling back to popup. Add frame-src https://openkey.so to your CSP.");
721
1367
  showToast();
722
1368
  const popupUrl = `${this.host}/widget/${action}?origin=${origin}`;
723
- this.openPopup(popupUrl, message, (val) => settle(() => resolve(val)), (err) => settle(() => reject(err)));
1369
+ this.openPopup(action, popupUrl, message, (val) => settle(() => resolve(val)), (err) => settle(() => reject(err)));
724
1370
  }, IFRAME_READY_TIMEOUT);
725
1371
  const overallTimeout = setTimeout(() => {
726
1372
  settle(() => {
@@ -728,8 +1374,24 @@ var OpenKey = class {
728
1374
  reject({ code: "TIMEOUT", message: "Request timed out" });
729
1375
  });
730
1376
  }, DEFAULT_TIMEOUT);
1377
+ cancel = () => {
1378
+ settle(() => {
1379
+ cleanup();
1380
+ reject({ code: "USER_CANCELLED", message: "Superseded by sign-out" });
1381
+ });
1382
+ };
1383
+ this.activeFlowCancellations.add(cancel);
731
1384
  });
732
1385
  }
1386
+ isResponseForAction(action, data) {
1387
+ const type = data.type;
1388
+ if (action === "connect") return type === "openkey:auth:response";
1389
+ if (action === "sign") return type === "openkey:sign:response" || type === "openkey:externalSign:approve";
1390
+ if (action === "sign-typed-data") return type === "openkey:signTypedData:response";
1391
+ if (action === "link-wallet") return type === "openkey:link-wallet:response";
1392
+ if (action === "sign-out") return type === "openkey:sign-out:response";
1393
+ return false;
1394
+ }
733
1395
  handleWalletLinkDelegation(modal) {
734
1396
  const sendResult = (result) => modal.postMessage(result);
735
1397
  new WalletPicker({
@@ -791,15 +1453,16 @@ var OpenKey = class {
791
1453
  }
792
1454
  });
793
1455
  }
794
- openPopup(url, message, resolve, reject) {
1456
+ openPopup(action, url, message, resolve, reject) {
795
1457
  const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
796
1458
  const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;
797
- this.popup = window.open(
1459
+ const popup = window.open(
798
1460
  url,
799
- "openkey",
1461
+ `openkey-${action}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
800
1462
  `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
801
1463
  );
802
- if (!this.popup) {
1464
+ this.popup = popup;
1465
+ if (!popup) {
803
1466
  showToast("Popup was blocked. Please allow popups for this site.", "error");
804
1467
  reject({
805
1468
  code: "POPUP_BLOCKED",
@@ -807,74 +1470,129 @@ var OpenKey = class {
807
1470
  });
808
1471
  return;
809
1472
  }
1473
+ let settled = false;
1474
+ let cancel;
810
1475
  const cleanup = () => {
811
1476
  window.removeEventListener("message", handleMessage);
812
1477
  clearInterval(pollClosed);
813
1478
  clearTimeout(timeout);
1479
+ this.activeFlowCancellations.delete(cancel);
1480
+ if (this.popup === popup) this.popup = null;
1481
+ };
1482
+ const settle = (fn) => {
1483
+ if (settled) return;
1484
+ settled = true;
1485
+ cleanup();
1486
+ fn();
814
1487
  };
815
1488
  const pollClosed = setInterval(() => {
816
- if (this.popup?.closed) {
817
- cleanup();
818
- reject({
1489
+ if (popup.closed) {
1490
+ settle(() => reject({
819
1491
  code: "USER_CANCELLED",
820
1492
  message: "User closed the popup"
821
- });
1493
+ }));
822
1494
  }
823
1495
  }, 500);
824
1496
  const timeout = setTimeout(() => {
825
- cleanup();
826
- this.popup?.close();
827
- reject({
828
- code: "TIMEOUT",
829
- message: "Request timed out"
1497
+ settle(() => {
1498
+ popup.close();
1499
+ reject({
1500
+ code: "TIMEOUT",
1501
+ message: "Request timed out"
1502
+ });
830
1503
  });
831
1504
  }, DEFAULT_TIMEOUT);
1505
+ const outgoingRequestId = message.requestId;
1506
+ const outgoingProtocolVersion = message.protocolVersion;
1507
+ const isVersionedRequest = typeof outgoingRequestId === "string" && typeof outgoingProtocolVersion === "number" && outgoingProtocolVersion >= 1;
832
1508
  const handleMessage = (event) => {
833
1509
  if (event.origin !== this.host) return;
834
- if (event.source !== this.popup) return;
1510
+ if (event.source !== popup) return;
835
1511
  const data = event.data;
836
1512
  if (data.type === "openkey:ready") {
837
- this.popup?.postMessage(message, this.host);
1513
+ popup.postMessage(message, this.host);
838
1514
  return;
839
1515
  }
840
1516
  if (data.type === "openkey:close") {
841
- cleanup();
842
- this.popup?.close();
843
- reject({
844
- code: "USER_CANCELLED",
845
- message: "User cancelled the request"
1517
+ if (isVersionedRequest) {
1518
+ const closeRequestId = data.requestId;
1519
+ const closeVersion = data.protocolVersion;
1520
+ if (closeRequestId !== outgoingRequestId) return;
1521
+ if (closeVersion !== outgoingProtocolVersion) return;
1522
+ }
1523
+ settle(() => {
1524
+ popup.close();
1525
+ reject({
1526
+ code: "USER_CANCELLED",
1527
+ message: "User cancelled the request"
1528
+ });
846
1529
  });
847
1530
  return;
848
1531
  }
849
1532
  if (data.type === "openkey:auth:use-external-wallet") {
850
- cleanup();
851
- this.popup?.close();
852
- this.handleExternalWalletConnect(resolve, reject);
1533
+ settle(() => {
1534
+ popup.close();
1535
+ this.handleExternalWalletConnect(resolve, reject);
1536
+ });
853
1537
  return;
854
1538
  }
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 });
1539
+ if (this.isResponseForAction(action, data)) {
1540
+ if (isVersionedRequest) {
1541
+ const respRequestId = data.requestId;
1542
+ const respVersion = data.protocolVersion;
1543
+ if (respRequestId !== outgoingRequestId) return;
1544
+ if (respVersion !== outgoingProtocolVersion) return;
1545
+ }
1546
+ settle(() => {
1547
+ popup.close();
1548
+ if (data.success) {
1549
+ if (data.type === "openkey:auth:response") {
1550
+ if (data.sessionToken) this.sessionToken = data.sessionToken;
1551
+ resolve({ address: data.address, keyId: data.keyId, keyType: data.keyType || "MANAGED" });
1552
+ } else if (data.type === "openkey:link-wallet:response") {
1553
+ resolve({ address: data.address, keyId: data.keyId });
1554
+ } else if (data.type === "openkey:sign-out:response") {
1555
+ resolve({ requestId: data.requestId, revoked: data.revoked });
1556
+ } else if (data.type === "openkey:externalSign:approve") {
1557
+ resolve({
1558
+ authorizationContextToken: data.authorizationContextToken,
1559
+ previewApprovalToken: data.previewApprovalToken,
1560
+ signedMessage: data.signedMessage,
1561
+ selectedActionIds: data.selectedActionIds,
1562
+ address: data.address
1563
+ });
1564
+ } else {
1565
+ resolve({
1566
+ signature: data.signature,
1567
+ address: data.address,
1568
+ signedMessage: data.signedMessage,
1569
+ selectedActionKeys: data.selectedActionKeys,
1570
+ permissions: data.permissions
1571
+ });
1572
+ }
864
1573
  } else {
865
- resolve({ signature: data.signature, address: data.address });
1574
+ reject(data.error);
866
1575
  }
867
- } else {
868
- reject(data.error);
869
- }
1576
+ });
870
1577
  }
871
1578
  };
872
1579
  window.addEventListener("message", handleMessage);
1580
+ cancel = () => {
1581
+ settle(() => {
1582
+ popup.close();
1583
+ reject({ code: "USER_CANCELLED", message: "Superseded by sign-out" });
1584
+ });
1585
+ };
1586
+ this.activeFlowCancellations.add(cancel);
1587
+ }
1588
+ cancelActiveFlows() {
1589
+ for (const cancel of [...this.activeFlowCancellations]) cancel();
873
1590
  }
874
1591
  /**
875
1592
  * Disconnect and close any open flows
876
1593
  */
877
1594
  disconnect() {
1595
+ this.cancelActiveFlows();
878
1596
  this.popup?.close();
879
1597
  this.popup = null;
880
1598
  }
@@ -947,8 +1665,13 @@ function getOpenKey(config) {
947
1665
  }
948
1666
  // Annotate the CommonJS export names for ESM import in node:
949
1667
  0 && (module.exports = {
1668
+ KeyIdTypeMismatchError,
950
1669
  OpenKey,
1670
+ OpenKeyNostr,
951
1671
  OpenKeyProvider,
952
- getOpenKey
1672
+ getOpenKey,
1673
+ resolveAuthorizeTinyCloudRouting,
1674
+ shouldRouteAuthorizeTinyCloudExternal,
1675
+ validateIframeResize
953
1676
  });
954
1677
  //# sourceMappingURL=index.js.map