@openkey/sdk 0.11.0 → 0.12.0-beta.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.mjs CHANGED
@@ -1,32 +1,32 @@
1
1
  // ../core/dist/index.js
2
- var u = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
- function g(e) {
4
- let r = "", n = e.length;
5
- for (let t = 0; t < n; t += 3) {
6
- 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;
7
- 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] : "";
8
- }
9
- return r.replace(/\+/g, "-").replace(/\//g, "_");
2
+ var y = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
+ function u(e) {
4
+ let n = "", t = e.length;
5
+ for (let r = 0; r < t; r += 3) {
6
+ let i = e[r], s = r + 1 < t ? e[r + 1] : 0, a = r + 2 < t ? e[r + 2] : 0, c = i << 16 | s << 8 | a;
7
+ n += y[c >> 18 & 63], n += y[c >> 12 & 63], n += r + 1 < t ? y[c >> 6 & 63] : "", n += r + 2 < t ? y[c & 63] : "";
8
+ }
9
+ return n.replace(/\+/g, "-").replace(/\//g, "_");
10
10
  }
11
- function y() {
11
+ function B() {
12
12
  let e = new Uint8Array(32);
13
- return crypto.getRandomValues(e), g(e);
13
+ return crypto.getRandomValues(e), u(e);
14
14
  }
15
- async function x(e) {
16
- let n = new TextEncoder().encode(e), t = await crypto.subtle.digest("SHA-256", n);
17
- return new Uint8Array(t);
15
+ async function I(e) {
16
+ let t = new TextEncoder().encode(e), r = await crypto.subtle.digest("SHA-256", t);
17
+ return new Uint8Array(r);
18
18
  }
19
- async function k(e, r) {
20
- let t = await (r ?? x)(e);
21
- return g(t);
19
+ async function W(e, n) {
20
+ let r = await (n ?? I)(e);
21
+ return u(r);
22
22
  }
23
- function m() {
23
+ function j() {
24
24
  let e = new Uint8Array(16);
25
- return crypto.getRandomValues(e), g(e);
25
+ return crypto.getRandomValues(e), u(e);
26
26
  }
27
- function T(e) {
28
- 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);
29
- 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();
27
+ function G(e) {
28
+ let { host: n, clientId: t, redirectUri: r, codeChallenge: i, state: s, scopes: a, authorizePath: c = "/api/auth/oauth2/authorize" } = e, d = new URL(c, n);
29
+ return d.searchParams.set("client_id", t), d.searchParams.set("redirect_uri", r), d.searchParams.set("response_type", "code"), d.searchParams.set("scope", a.join(" ")), d.searchParams.set("state", s), d.searchParams.set("code_challenge", i), d.searchParams.set("code_challenge_method", "S256"), d.toString();
30
30
  }
31
31
 
32
32
  // src/nostr.ts
@@ -230,6 +230,33 @@ var OpenKeyNostr = class {
230
230
  }
231
231
  };
232
232
 
233
+ // src/popup-capability.ts
234
+ function isEmbeddedAppWebView(env) {
235
+ const target = env ?? (typeof window === "undefined" ? void 0 : window);
236
+ if (!target) return false;
237
+ if (target.Capacitor?.isNativePlatform?.() === true) return true;
238
+ if (target.ReactNativeWebView) return true;
239
+ const userAgent = target.navigator?.userAgent ?? "";
240
+ if (/\bAndroid\b[^)]*;\s*wv\b/.test(userAgent)) return true;
241
+ if (/\b(iPhone|iPad|iPod)\b/.test(userAgent) && /AppleWebKit/.test(userAgent) && !/Safari\//.test(userAgent)) {
242
+ return true;
243
+ }
244
+ return false;
245
+ }
246
+ function isUsablePopup(popup) {
247
+ return !!popup && popup.closed !== true;
248
+ }
249
+ function popupUnavailableError(embeddedWebView) {
250
+ if (embeddedWebView) {
251
+ return {
252
+ code: "POPUP_BLOCKED",
253
+ message: "Popups are not available in this embedded app WebView. Make sure the OpenKey host is allowed by frame-src, or use native sign-in.",
254
+ reason: "embedded-webview"
255
+ };
256
+ }
257
+ return { code: "POPUP_BLOCKED", message: "Popup was blocked. Please allow popups for this site." };
258
+ }
259
+
233
260
  // src/index.ts
234
261
  function passkeysQueryFlag(passkeysSupported) {
235
262
  return passkeysSupported ? "" : "&passkeys=false";
@@ -286,9 +313,9 @@ function resolveAuthorizeTinyCloudRouting(state) {
286
313
  const requestKeyId = rawRequestKeyId && rawRequestKeyId.length > 0 ? rawRequestKeyId : null;
287
314
  const knownKeys = state.knownKeys ?? [];
288
315
  const lookupType = (id) => {
289
- for (const k2 of knownKeys) {
290
- if (k2.id === id) return k2.keyType;
291
- if (k2.address && k2.address.toLowerCase() === id.toLowerCase()) return k2.keyType;
316
+ for (const k of knownKeys) {
317
+ if (k.id === id) return k.keyType;
318
+ if (k.address && k.address.toLowerCase() === id.toLowerCase()) return k.keyType;
292
319
  }
293
320
  if (id.startsWith("external:")) return "EXTERNAL";
294
321
  return null;
@@ -651,6 +678,12 @@ var OpenKey = class {
651
678
  * the SDK never holds an OpenKey session token. Local SDK state is cleared
652
679
  * before opening the widget, including when remote revocation is
653
680
  * unavailable.
681
+ *
682
+ * Rejects without waiting for the timeout when the widget cannot be shown
683
+ * (code `POPUP_BLOCKED`): the iframe did not load and the popup fallback is
684
+ * blocked, or the app is an embedded WebView that cannot open popups.
685
+ * Remote revocation did not happen then, so clearing any app session is the
686
+ * caller's decision.
654
687
  */
655
688
  async signOut(opts) {
656
689
  const requestId = `ok-signout-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
@@ -1045,14 +1078,14 @@ var OpenKey = class {
1045
1078
  * @returns Promise that resolves with authorization code after user consent
1046
1079
  */
1047
1080
  connect: async (config) => {
1048
- const verifier = y();
1049
- const challenge = await k(verifier);
1050
- const state = config.state || m();
1081
+ const verifier = B();
1082
+ const challenge = await W(verifier);
1083
+ const state = config.state || j();
1051
1084
  sessionStorage.setItem(
1052
1085
  OAUTH_STORAGE_KEY,
1053
1086
  JSON.stringify({ verifier, state })
1054
1087
  );
1055
- const authUrl = T({
1088
+ const authUrl = G({
1056
1089
  host: this.oauthHost,
1057
1090
  clientId: config.clientId,
1058
1091
  redirectUri: config.redirectUri,
@@ -1141,19 +1174,19 @@ var OpenKey = class {
1141
1174
  openOAuthPopup(url, state, redirectUri, resolve, reject) {
1142
1175
  const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
1143
1176
  const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;
1144
- this.popup = window.open(
1177
+ const popup = window.open(
1145
1178
  url,
1146
1179
  "openkey-oauth",
1147
1180
  `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
1148
1181
  );
1149
- if (!this.popup) {
1150
- showToast("Popup was blocked. Please allow popups for this site.", "error");
1151
- reject({
1152
- code: "POPUP_BLOCKED",
1153
- message: "Popup was blocked. Please allow popups or use redirect mode."
1154
- });
1182
+ if (!isUsablePopup(popup)) {
1183
+ this.popup = null;
1184
+ const embedded = isEmbeddedAppWebView();
1185
+ if (!embedded) showToast("Popup was blocked. Please allow popups for this site.", "error");
1186
+ reject(embedded ? popupUnavailableError(true) : { code: "POPUP_BLOCKED", message: "Popup was blocked. Please allow popups or use redirect mode." });
1155
1187
  return;
1156
1188
  }
1189
+ this.popup = popup;
1157
1190
  const redirectHost = new URL(redirectUri).origin;
1158
1191
  const pollInterval = setInterval(() => {
1159
1192
  try {
@@ -1337,6 +1370,11 @@ var OpenKey = class {
1337
1370
  const readyTimeout = setTimeout(() => {
1338
1371
  if (readyReceived || settled) return;
1339
1372
  cleanup();
1373
+ if (isEmbeddedAppWebView()) {
1374
+ console.warn("OpenKey: iframe did not load in an embedded app WebView, which cannot open a popup. Add frame-src https://openkey.so to your CSP.");
1375
+ settle(() => reject(popupUnavailableError(true)));
1376
+ return;
1377
+ }
1340
1378
  console.warn("OpenKey: iframe blocked by CSP, falling back to popup. Add frame-src https://openkey.so to your CSP.");
1341
1379
  showToast();
1342
1380
  const popupUrl = `${this.host}/widget/${action}?origin=${origin}${passkeysQueryFlag(this.passkeysSupported)}`;
@@ -1435,15 +1473,14 @@ var OpenKey = class {
1435
1473
  `openkey-${action}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
1436
1474
  `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
1437
1475
  );
1438
- this.popup = popup;
1439
- if (!popup) {
1440
- showToast("Popup was blocked. Please allow popups for this site.", "error");
1441
- reject({
1442
- code: "POPUP_BLOCKED",
1443
- message: "Popup was blocked. Please allow popups for this site."
1444
- });
1476
+ if (!isUsablePopup(popup)) {
1477
+ this.popup = null;
1478
+ const embedded = isEmbeddedAppWebView();
1479
+ if (!embedded) showToast("Popup was blocked. Please allow popups for this site.", "error");
1480
+ reject(popupUnavailableError(embedded));
1445
1481
  return;
1446
1482
  }
1483
+ this.popup = popup;
1447
1484
  let settled = false;
1448
1485
  let cancel;
1449
1486
  const cleanup = () => {