@bwalletx/connect 0.3.0 → 0.4.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.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { WalletInterface, WalletProtocol } from '@bsv/sdk';
2
+ import { f as PairingOptions, W as WalletOption } from './wallets-VOKIKBbO.js';
3
+ export { g as BRC29_PROTOCOL, i as Brc29Remittance, B as BuyTokenRequest, C as ConnectedWallet, D as DiscoverWalletsOptions, H as HandCashAdapterOptions, L as LegacyPayer, j as PairConnectionOptions, k as PairStorage, l as PairedWallet, b as PayOptions, m as PayOutput, P as PayRequest, a as PayResult, n as PaySource, c as PaymentError, o as PaymentErrorCode, S as SocketFactory, q as SocketLike, T as TokenQuote, r as WalletKind, s as amountInSats, d as brc100Option, t as buyToken, u as clearPairing, e as discoverWallets, h as handcashAdapter, v as isIdentityKeyHex, w as isPaymail, x as pairBwalletX, p as pairingOption, y as pay, z as resolvePaymailP2P, A as restorePairing, E as tip } from './wallets-VOKIKBbO.js';
2
4
  export { AuthorizeParams, GET_URL, SIGNIN_CSS, SIGNIN_TAG, SigninBrand, SigninLabel, SigninSize, SigninTheme, WEB_WALLET_URL, buildAuthorizeUrl, defineBwalletxSignin, markSvg, signinText } from './element.js';
3
5
 
4
6
  /** Ids bWalletX announces in BRC-100 wallet discovery. */
@@ -24,67 +26,6 @@ declare function discoverBwalletX(ms?: number): Promise<FoundWallet | null>;
24
26
  */
25
27
  declare function findBwalletX(ms?: number): Promise<WalletInterface | null>;
26
28
 
27
- /** localStorage-shaped storage. Methods may be sync or async. */
28
- interface PairStorage {
29
- getItem(key: string): string | null | Promise<string | null>;
30
- setItem(key: string, value: string): void | Promise<void>;
31
- removeItem(key: string): void | Promise<void>;
32
- }
33
- /** The minimum of the browser WebSocket API this package uses (the `ws` package also fits). */
34
- interface SocketLike {
35
- readonly readyState: number;
36
- onopen: ((ev: unknown) => void) | null;
37
- onmessage: ((ev: {
38
- data: unknown;
39
- }) => void) | null;
40
- onerror: ((ev: unknown) => void) | null;
41
- onclose: ((ev: unknown) => void) | null;
42
- send(data: string): void;
43
- close(): void;
44
- }
45
- type SocketFactory = (url: string) => SocketLike;
46
- interface PairConnectionOptions {
47
- /** Where the pairing is kept so it survives a reload. Default: localStorage, else memory only. */
48
- storage?: PairStorage | null;
49
- /** Storage key. Default 'bwalletx.connect.pairing.v1'. */
50
- storageKey?: string;
51
- /** Opens a socket. Default: the global WebSocket. Pass one to run in Node or to test. */
52
- socket?: SocketFactory;
53
- /** Per-call timeout in ms. Default 120000. */
54
- callTimeoutMs?: number;
55
- }
56
- interface PairingOptions extends PairConnectionOptions {
57
- /** Show this link as a QR code, and as text to copy (web.bwalletx.com takes it pasted). */
58
- onLink: (link: string) => void;
59
- /** Show this 4-digit code. The wallet shows the same one; the user checks they match. */
60
- onCode: (code: string) => void;
61
- signal?: AbortSignal;
62
- /** Relay host. Default relay.bwallet.space. */
63
- relay?: string;
64
- /** This page's origin, as the relay will see it. Default location.origin. */
65
- origin?: string;
66
- }
67
- /** A BRC-100 wallet whose every call goes, end-to-end encrypted, to the paired phone or web wallet. */
68
- type PairedWallet = WalletInterface & {
69
- /** Close the relay connection. The pairing stays stored; the next call reconnects. */
70
- close(): void;
71
- /** Tell the wallet to unpair, close, and delete the stored pairing. */
72
- forget(): Promise<void>;
73
- };
74
- /**
75
- * Pair with bWalletX on a phone (scan the QR) or web.bwalletx.com (paste the link).
76
- * Resolves with a wallet once the user taps Connect. The pairing is stored (see `storage`) so
77
- * `restorePairing()` can reconnect after a reload without a new QR.
78
- */
79
- declare function pairBwalletX(opts: PairingOptions): Promise<PairedWallet>;
80
- /**
81
- * The wallet from a pairing stored by an earlier `pairBwalletX`, or null if there is none.
82
- * It connects on the first call. The wallet must still have this site paired.
83
- */
84
- declare function restorePairing(opts?: PairConnectionOptions): Promise<PairedWallet | null>;
85
- /** Delete the stored pairing without telling the wallet. */
86
- declare function clearPairing(opts?: Pick<PairConnectionOptions, 'storage' | 'storageKey'>): Promise<void>;
87
-
88
29
  /** Security level 2, a protocol name used for nothing else. */
89
30
  declare const LOGIN_PROTOCOL: WalletProtocol;
90
31
  /** What your server's challenge route returns (createChallenge from '@bwalletx/connect/server'). */
@@ -146,4 +87,24 @@ declare function renderButton(el: HTMLElement | string, opts?: ButtonOptions): H
146
87
  */
147
88
  declare function inOrder<T>(handle: (frame: T) => Promise<void> | void, onError?: (e: unknown) => void): (frame: T) => Promise<void>;
148
89
 
149
- export { BWALLETX_RDNS, type ButtonOptions, type Challenge, type FoundBy, type FoundWallet, LOGIN_PROTOCOL, LOGO_URL, type PairConnectionOptions, type PairStorage, type PairedWallet, type PairingOptions, RDNS_EXTENSION, RDNS_MOBILE, type SignInMethod, type SignInOptions, type SignInResult, type SocketFactory, type SocketLike, clearPairing, connectBwalletX, discoverBwalletX, findBwalletX, inOrder, pairBwalletX, renderButton, restorePairing, signInWithBwalletX };
90
+ /** An SVG QR code for `text` (error correction M). */
91
+ declare function qrSvg(text: string): string;
92
+ interface PairingModalOptions {
93
+ title?: string;
94
+ /** Called when the user closes the modal (the pairing is then aborted). */
95
+ onCancel?: () => void;
96
+ }
97
+ /**
98
+ * PairingOptions that show a QR modal. Pass to signInWithBwalletX({ pairing: pairingModal() }) or
99
+ * connectBwalletX. The modal opens when the link is ready and shows the match code. Call `close()`
100
+ * once pairing settles (useBwalletX does this for you); Cancel aborts the pairing.
101
+ */
102
+ declare function pairingModal(o?: PairingModalOptions): PairingOptions & {
103
+ close(): void;
104
+ };
105
+ /** Show the picker; resolves with the chosen option, or null if closed. */
106
+ declare function pickWallet(options: WalletOption[], o?: {
107
+ title?: string;
108
+ }): Promise<WalletOption | null>;
109
+
110
+ export { BWALLETX_RDNS, type ButtonOptions, type Challenge, type FoundBy, type FoundWallet, LOGIN_PROTOCOL, LOGO_URL, type PairingModalOptions, PairingOptions, RDNS_EXTENSION, RDNS_MOBILE, type SignInMethod, type SignInOptions, type SignInResult, WalletOption, connectBwalletX, discoverBwalletX, findBwalletX, inOrder, pairingModal, pickWallet, qrSvg, renderButton, signInWithBwalletX };
package/dist/index.js CHANGED
@@ -1,3 +1,21 @@
1
+ import {
2
+ BRC29_PROTOCOL,
3
+ PaymentError,
4
+ amountInSats,
5
+ brc100Option,
6
+ buyToken,
7
+ discoverWallets,
8
+ handcashAdapter,
9
+ isIdentityKeyHex,
10
+ isPaymail,
11
+ pairingModal,
12
+ pairingOption,
13
+ pay,
14
+ pickWallet,
15
+ qrSvg,
16
+ resolvePaymailP2P,
17
+ tip
18
+ } from "./chunk-FUQ46RJV.js";
1
19
  import {
2
20
  BWALLETX_RDNS,
3
21
  GET_URL,
@@ -65,27 +83,43 @@ function renderButton(el, opts = {}) {
65
83
  return b;
66
84
  }
67
85
  export {
86
+ BRC29_PROTOCOL,
68
87
  BWALLETX_RDNS,
69
88
  GET_URL,
70
89
  LOGIN_PROTOCOL,
71
90
  LOGO_URL,
91
+ PaymentError,
72
92
  RDNS_EXTENSION,
73
93
  RDNS_MOBILE,
74
94
  SIGNIN_CSS,
75
95
  SIGNIN_TAG,
76
96
  WEB_WALLET_URL,
97
+ amountInSats,
98
+ brc100Option,
77
99
  buildAuthorizeUrl,
100
+ buyToken,
78
101
  clearPairing,
79
102
  connectBwalletX,
80
103
  defineBwalletxSignin,
81
104
  discoverBwalletX,
105
+ discoverWallets,
82
106
  findBwalletX,
107
+ handcashAdapter,
83
108
  inOrder,
109
+ isIdentityKeyHex,
110
+ isPaymail,
84
111
  markSvg,
85
112
  pairBwalletX,
113
+ pairingModal,
114
+ pairingOption,
115
+ pay,
116
+ pickWallet,
117
+ qrSvg,
86
118
  renderButton,
119
+ resolvePaymailP2P,
87
120
  restorePairing,
88
121
  signInWithBwalletX,
89
- signinText
122
+ signinText,
123
+ tip
90
124
  };
91
125
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/button.ts"],"sourcesContent":["const CSS = `\n.bwx-signin{display:inline-flex;align-items:center;gap:12px;min-height:48px;padding:10px 22px 10px 10px;border:0;border-radius:12px;cursor:pointer;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;text-align:left;font:700 16px/1.2 system-ui,-apple-system,\"Segoe UI\",sans-serif;box-shadow:0 8px 24px rgba(245,158,11,.2)}\n.bwx-signin:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}\n.bwx-signin:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}\n.bwx-signin:disabled{opacity:.6;cursor:wait}\n.bwx-signin .bwx-mark{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background:#000;flex:none}\n.bwx-signin small{display:block;font-size:11px;font-weight:500;color:rgba(0,0,0,.6)}`;\n\nexport const LOGO_URL = 'https://bwalletx.com/logo.svg';\n\nexport interface ButtonOptions {\n /** Second line under the label. Default \"also works with bWallet\". false hides it. */\n subtitle?: string | false;\n onClick?: (button: HTMLButtonElement) => void;\n}\n\n/** Put the gold \"Sign in with bWalletX\" button into `el` (replacing its contents). */\nexport function renderButton(el: HTMLElement | string, opts: ButtonOptions = {}): HTMLButtonElement {\n const host = typeof el === 'string' ? document.querySelector<HTMLElement>(el) : el;\n if (!host) throw new Error(`renderButton: no element ${String(el)}`);\n if (!document.getElementById('bwx-signin-css')) {\n const style = document.createElement('style');\n style.id = 'bwx-signin-css';\n style.textContent = CSS;\n document.head.appendChild(style);\n }\n const b = document.createElement('button');\n b.type = 'button';\n b.className = 'bwx-signin';\n const mark = document.createElement('span');\n mark.className = 'bwx-mark';\n const img = document.createElement('img');\n img.src = LOGO_URL;\n img.alt = '';\n img.width = 15;\n img.height = 20;\n mark.appendChild(img);\n const label = document.createElement('span');\n label.className = 'bwx-label';\n label.textContent = 'Sign in with bWalletX';\n const sub = opts.subtitle === undefined ? 'also works with bWallet' : opts.subtitle;\n if (sub) {\n const small = document.createElement('small');\n small.textContent = sub;\n label.appendChild(small);\n }\n b.append(mark, label);\n if (opts.onClick) b.addEventListener('click', () => opts.onClick!(b));\n host.replaceChildren(b);\n return b;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQL,IAAM,WAAW;AASjB,SAAS,aAAa,IAA0B,OAAsB,CAAC,GAAsB;AAClG,QAAM,OAAO,OAAO,OAAO,WAAW,SAAS,cAA2B,EAAE,IAAI;AAChF,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,4BAA4B,OAAO,EAAE,CAAC,EAAE;AACnE,MAAI,CAAC,SAAS,eAAe,gBAAgB,GAAG;AAC9C,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,KAAK;AACX,UAAM,cAAc;AACpB,aAAS,KAAK,YAAY,KAAK;AAAA,EACjC;AACA,QAAM,IAAI,SAAS,cAAc,QAAQ;AACzC,IAAE,OAAO;AACT,IAAE,YAAY;AACd,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,OAAK,YAAY;AACjB,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,MAAM;AACV,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,MAAI,SAAS;AACb,OAAK,YAAY,GAAG;AACpB,QAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,QAAM,YAAY;AAClB,QAAM,cAAc;AACpB,QAAM,MAAM,KAAK,aAAa,SAAY,4BAA4B,KAAK;AAC3E,MAAI,KAAK;AACP,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,cAAc;AACpB,UAAM,YAAY,KAAK;AAAA,EACzB;AACA,IAAE,OAAO,MAAM,KAAK;AACpB,MAAI,KAAK,QAAS,GAAE,iBAAiB,SAAS,MAAM,KAAK,QAAS,CAAC,CAAC;AACpE,OAAK,gBAAgB,CAAC;AACtB,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/button.ts"],"sourcesContent":["const CSS = `\n.bwx-signin{display:inline-flex;align-items:center;gap:12px;min-height:48px;padding:10px 22px 10px 10px;border:0;border-radius:12px;cursor:pointer;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;text-align:left;font:700 16px/1.2 system-ui,-apple-system,\"Segoe UI\",sans-serif;box-shadow:0 8px 24px rgba(245,158,11,.2)}\n.bwx-signin:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}\n.bwx-signin:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}\n.bwx-signin:disabled{opacity:.6;cursor:wait}\n.bwx-signin .bwx-mark{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background:#000;flex:none}\n.bwx-signin small{display:block;font-size:11px;font-weight:500;color:rgba(0,0,0,.6)}`;\n\nexport const LOGO_URL = 'https://bwalletx.com/logo.svg';\n\nexport interface ButtonOptions {\n /** Second line under the label. Default \"also works with bWallet\". false hides it. */\n subtitle?: string | false;\n onClick?: (button: HTMLButtonElement) => void;\n}\n\n/** Put the gold \"Sign in with bWalletX\" button into `el` (replacing its contents). */\nexport function renderButton(el: HTMLElement | string, opts: ButtonOptions = {}): HTMLButtonElement {\n const host = typeof el === 'string' ? document.querySelector<HTMLElement>(el) : el;\n if (!host) throw new Error(`renderButton: no element ${String(el)}`);\n if (!document.getElementById('bwx-signin-css')) {\n const style = document.createElement('style');\n style.id = 'bwx-signin-css';\n style.textContent = CSS;\n document.head.appendChild(style);\n }\n const b = document.createElement('button');\n b.type = 'button';\n b.className = 'bwx-signin';\n const mark = document.createElement('span');\n mark.className = 'bwx-mark';\n const img = document.createElement('img');\n img.src = LOGO_URL;\n img.alt = '';\n img.width = 15;\n img.height = 20;\n mark.appendChild(img);\n const label = document.createElement('span');\n label.className = 'bwx-label';\n label.textContent = 'Sign in with bWalletX';\n const sub = opts.subtitle === undefined ? 'also works with bWallet' : opts.subtitle;\n if (sub) {\n const small = document.createElement('small');\n small.textContent = sub;\n label.appendChild(small);\n }\n b.append(mark, label);\n if (opts.onClick) b.addEventListener('click', () => opts.onClick!(b));\n host.replaceChildren(b);\n return b;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQL,IAAM,WAAW;AASjB,SAAS,aAAa,IAA0B,OAAsB,CAAC,GAAsB;AAClG,QAAM,OAAO,OAAO,OAAO,WAAW,SAAS,cAA2B,EAAE,IAAI;AAChF,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,4BAA4B,OAAO,EAAE,CAAC,EAAE;AACnE,MAAI,CAAC,SAAS,eAAe,gBAAgB,GAAG;AAC9C,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,KAAK;AACX,UAAM,cAAc;AACpB,aAAS,KAAK,YAAY,KAAK;AAAA,EACjC;AACA,QAAM,IAAI,SAAS,cAAc,QAAQ;AACzC,IAAE,OAAO;AACT,IAAE,YAAY;AACd,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,OAAK,YAAY;AACjB,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,MAAM;AACV,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,MAAI,SAAS;AACb,OAAK,YAAY,GAAG;AACpB,QAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,QAAM,YAAY;AAClB,QAAM,cAAc;AACpB,QAAM,MAAM,KAAK,aAAa,SAAY,4BAA4B,KAAK;AAC3E,MAAI,KAAK;AACP,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,cAAc;AACpB,UAAM,YAAY,KAAK;AAAA,EACzB;AACA,IAAE,OAAO,MAAM,KAAK;AACpB,MAAI,KAAK,QAAS,GAAE,iBAAiB,SAAS,MAAM,KAAK,QAAS,CAAC,CAAC;AACpE,OAAK,gBAAgB,CAAC;AACtB,SAAO;AACT;","names":[]}
package/dist/next.cjs ADDED
@@ -0,0 +1,415 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/next.ts
31
+ var next_exports = {};
32
+ __export(next_exports, {
33
+ IdentityLinkConflictError: () => IdentityLinkConflictError,
34
+ createBwalletXAuth: () => createBwalletXAuth,
35
+ linkIdentity: () => linkIdentity,
36
+ memoryStore: () => memoryStore,
37
+ pgNonceTableSql: () => pgNonceTableSql,
38
+ pgStore: () => pgStore,
39
+ redisStore: () => redisStore,
40
+ signInOrLink: () => signInOrLink
41
+ });
42
+ module.exports = __toCommonJS(next_exports);
43
+
44
+ // src/link.ts
45
+ var IdentityLinkConflictError = class extends Error {
46
+ constructor(identityKey, otherUserId) {
47
+ super("This bWalletX identity is already linked to a different account. Sign in with that account, or unlink it there first.");
48
+ this.identityKey = identityKey;
49
+ this.otherUserId = otherUserId;
50
+ this.name = "IdentityLinkConflictError";
51
+ }
52
+ identityKey;
53
+ otherUserId;
54
+ code = "IDENTITY_LINKED_TO_OTHER_USER";
55
+ };
56
+ var norm = (k) => {
57
+ if (typeof k !== "string" || !/^0[23][0-9a-fA-F]{64}$/.test(k)) throw new Error("identityKey must be a compressed public key (66 hex chars)");
58
+ return k.toLowerCase();
59
+ };
60
+ async function linkIdentity(userId, identityKey, cb) {
61
+ if (!userId) throw new Error("linkIdentity needs the current user id");
62
+ const key = norm(identityKey);
63
+ const owner = await cb.findUserByIdentityKey(key);
64
+ if (owner && owner !== userId) throw new IdentityLinkConflictError(key, owner);
65
+ if (owner === userId) return { status: "already-linked", userId, identityKey: key };
66
+ await cb.attach(userId, key);
67
+ return { status: "linked", userId, identityKey: key };
68
+ }
69
+ async function signInOrLink(identityKey, o) {
70
+ const key = norm(identityKey);
71
+ if (o.currentUserId) return linkIdentity(o.currentUserId, key, o);
72
+ const owner = await o.findUserByIdentityKey(key);
73
+ if (owner) return { status: "signed-in", userId: owner, identityKey: key };
74
+ if (!o.createUser) return { status: "unknown", userId: null, identityKey: key };
75
+ const userId = await o.createUser(key);
76
+ await o.attach(userId, key);
77
+ return { status: "created", userId, identityKey: key };
78
+ }
79
+
80
+ // src/server.ts
81
+ var import_sdk = require("@bsv/sdk");
82
+
83
+ // src/nonce-stores.ts
84
+ var ttlSeconds = (c) => Math.max(1, Math.ceil((c.expiresAt - Date.now()) / 1e3));
85
+ function parse(v) {
86
+ if (v == null) return null;
87
+ try {
88
+ const c = typeof v === "string" ? JSON.parse(v) : v;
89
+ if (!c || typeof c.identityKey !== "string" || typeof c.message !== "string" || typeof c.expiresAt !== "number") return null;
90
+ return c.expiresAt < Date.now() ? null : c;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ function redisStore(opts) {
96
+ const { client } = opts;
97
+ const prefix = opts.prefix ?? "bwalletx:nonce:";
98
+ const getdel = client.getdel?.bind(client) ?? client.getDel?.bind(client);
99
+ if (!getdel) throw new Error("redisStore: the client needs getdel (or getDel). Redis 6.2+.");
100
+ return {
101
+ async put(nonce, c) {
102
+ const s = ttlSeconds(c);
103
+ if (opts.flavor === "ioredis") await client.set(prefix + nonce, JSON.stringify(c), "EX", s);
104
+ else await client.set(prefix + nonce, JSON.stringify(c), { ex: s, EX: s });
105
+ },
106
+ async take(nonce) {
107
+ return parse(await getdel(prefix + nonce));
108
+ }
109
+ };
110
+ }
111
+ function pgNonceTableSql(table = "bwalletx_nonces") {
112
+ const t = safeTable(table);
113
+ return `create table if not exists ${t} (
114
+ nonce text primary key,
115
+ challenge jsonb not null,
116
+ expires_at timestamptz not null
117
+ );
118
+ create index if not exists ${t.replace(".", "_")}_expires_idx on ${t} (expires_at);`;
119
+ }
120
+ function safeTable(t) {
121
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(t)) throw new Error(`pgStore: bad table name ${t}`);
122
+ return t;
123
+ }
124
+ function pgStore(opts) {
125
+ const t = safeTable(opts.table ?? "bwalletx_nonces");
126
+ const rows = (r) => Array.isArray(r) ? r : r.rows;
127
+ return {
128
+ async put(nonce, c) {
129
+ await opts.query(`delete from ${t} where expires_at < now()`, []);
130
+ await opts.query(`insert into ${t} (nonce, challenge, expires_at) values ($1, $2, to_timestamp($3 / 1000.0))`, [
131
+ nonce,
132
+ JSON.stringify(c),
133
+ c.expiresAt
134
+ ]);
135
+ },
136
+ async take(nonce) {
137
+ const r = rows(await opts.query(`delete from ${t} where nonce = $1 returning challenge`, [nonce]));
138
+ const row = r[0];
139
+ return parse(row?.challenge);
140
+ }
141
+ };
142
+ }
143
+
144
+ // src/session.ts
145
+ var MIN_SECRET_LENGTH = 32;
146
+ var enc = new TextEncoder();
147
+ var dec = new TextDecoder();
148
+ function b64u(bytes) {
149
+ let s = "";
150
+ for (const b of bytes) s += String.fromCharCode(b);
151
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
152
+ }
153
+ function unb64u(s) {
154
+ if (!/^[A-Za-z0-9_-]*$/.test(s)) throw new Error("bad base64url");
155
+ const b = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
156
+ return Uint8Array.from(b, (c) => c.charCodeAt(0));
157
+ }
158
+ function assertSecret(secret) {
159
+ if (typeof secret !== "string" || secret.length < MIN_SECRET_LENGTH) {
160
+ throw new Error(
161
+ `[@bwalletx/connect] A session secret of at least ${MIN_SECRET_LENGTH} characters is required. Set it in an environment variable (e.g. BWALLETX_SESSION_SECRET). There is no default.`
162
+ );
163
+ }
164
+ }
165
+ var keys = /* @__PURE__ */ new Map();
166
+ function hmacKey(secret) {
167
+ let k = keys.get(secret);
168
+ if (!k) {
169
+ k = globalThis.crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
170
+ keys.set(secret, k);
171
+ }
172
+ return k;
173
+ }
174
+ async function signSession(claims, opts) {
175
+ assertSecret(opts.secret);
176
+ if (typeof claims.sub !== "string" || !claims.sub) throw new Error("session needs a sub (identity key)");
177
+ const iat = Math.floor((opts.now ?? Date.now()) / 1e3);
178
+ const payload = { ...claims, sub: claims.sub, iat, exp: iat + (opts.maxAge ?? 7 * 24 * 3600) };
179
+ const body2 = b64u(enc.encode(JSON.stringify(payload)));
180
+ const sig = new Uint8Array(await globalThis.crypto.subtle.sign("HMAC", await hmacKey(opts.secret), enc.encode(body2)));
181
+ return `${body2}.${b64u(sig)}`;
182
+ }
183
+ async function verifySession(token, opts) {
184
+ assertSecret(opts.secret);
185
+ if (typeof token !== "string" || token.length > 8192) return null;
186
+ const dot = token.indexOf(".");
187
+ if (dot < 1 || dot !== token.lastIndexOf(".")) return null;
188
+ const body2 = token.slice(0, dot);
189
+ try {
190
+ const sig = unb64u(token.slice(dot + 1));
191
+ const ok = await globalThis.crypto.subtle.verify("HMAC", await hmacKey(opts.secret), sig, enc.encode(body2));
192
+ if (!ok) return null;
193
+ const p = JSON.parse(dec.decode(unb64u(body2)));
194
+ if (!p || typeof p.sub !== "string" || typeof p.exp !== "number") return null;
195
+ if (Math.floor((opts.now ?? Date.now()) / 1e3) >= p.exp) return null;
196
+ return p;
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+ function serializeCookie(value, o) {
202
+ const parts = [`${o.name}=${value}`, `Path=${o.path ?? "/"}`, `Max-Age=${o.maxAge}`, "HttpOnly", `SameSite=${o.sameSite ?? "Lax"}`];
203
+ if (o.secure !== false) parts.push("Secure");
204
+ if (o.domain) parts.push(`Domain=${o.domain}`);
205
+ return parts.join("; ");
206
+ }
207
+ function readCookie(header, name) {
208
+ if (!header) return null;
209
+ for (const part of header.split(";")) {
210
+ const i = part.indexOf("=");
211
+ if (i > 0 && part.slice(0, i).trim() === name) return part.slice(i + 1).trim();
212
+ }
213
+ return null;
214
+ }
215
+
216
+ // src/server.ts
217
+ var LOGIN_PROTOCOL = [2, "bwallet sign in"];
218
+ var CHALLENGE_LIFETIME_MS = 2 * 60 * 1e3;
219
+ var warned = false;
220
+ function memoryStore(opts = {}) {
221
+ const prod = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
222
+ if ((prod || !opts.quiet) && !warned) {
223
+ warned = true;
224
+ console.warn(
225
+ prod ? "[@bwalletx/connect] WARNING: the in-memory nonce store is running in production. It is not shared between instances and breaks on serverless. Use redisStore or pgStore." : "[@bwalletx/connect] Using the in-memory nonce store. It is for development only; pass a shared store (redisStore, pgStore) in production."
226
+ );
227
+ }
228
+ const m = /* @__PURE__ */ new Map();
229
+ return {
230
+ put(nonce, c) {
231
+ const now = Date.now();
232
+ for (const [k, v] of m) if (v.expiresAt < now) m.delete(k);
233
+ m.set(nonce, c);
234
+ },
235
+ take(nonce) {
236
+ const c = m.get(nonce) ?? null;
237
+ m.delete(nonce);
238
+ return c;
239
+ }
240
+ };
241
+ }
242
+ var defaultStore = null;
243
+ var storeOrDefault = (s) => s ?? (defaultStore ??= memoryStore());
244
+ function isIdentityKey(x) {
245
+ if (typeof x !== "string" || !/^0[23][0-9a-fA-F]{64}$/.test(x)) return false;
246
+ try {
247
+ import_sdk.PublicKey.fromString(x);
248
+ return true;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+ function normalOrigin(origin) {
254
+ const u = new URL(origin);
255
+ if (u.origin === "null" || u.origin !== origin.replace(/\/$/, "")) {
256
+ throw new Error(`origin must be scheme://host[:port], got ${origin}`);
257
+ }
258
+ return u.origin;
259
+ }
260
+ function loginMessage(origin, nonce, expiresAt) {
261
+ return [
262
+ `Sign in to ${new URL(origin).host} with bWallet`,
263
+ "",
264
+ "This proves you hold this wallet. It does not spend anything.",
265
+ "",
266
+ `Origin: ${origin}`,
267
+ `Nonce: ${nonce}`,
268
+ `Expires: ${new Date(expiresAt).toISOString()}`
269
+ ].join("\n");
270
+ }
271
+ function randomNonce() {
272
+ const b = new Uint8Array(16);
273
+ globalThis.crypto.getRandomValues(b);
274
+ let s = "";
275
+ for (const x of b) s += String.fromCharCode(x);
276
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
277
+ }
278
+ async function createChallenge(opts) {
279
+ if (!isIdentityKey(opts.identityKey)) throw new Error("identityKey must be a compressed public key (66 hex chars)");
280
+ const origin = normalOrigin(opts.origin);
281
+ const nonce = randomNonce();
282
+ const expiresAt = Date.now() + CHALLENGE_LIFETIME_MS;
283
+ const message = loginMessage(origin, nonce, expiresAt);
284
+ await storeOrDefault(opts.store).put(nonce, { identityKey: opts.identityKey.toLowerCase(), origin, message, expiresAt });
285
+ return { nonce, message, protocolID: LOGIN_PROTOCOL, keyID: nonce, expiresAt };
286
+ }
287
+ async function verifySignIn(opts) {
288
+ if (typeof opts.nonce !== "string" || opts.nonce.length > 64) return null;
289
+ const c = await storeOrDefault(opts.store).take(opts.nonce);
290
+ if (!c || Date.now() > c.expiresAt) return null;
291
+ let origin;
292
+ try {
293
+ origin = normalOrigin(opts.origin);
294
+ } catch {
295
+ return null;
296
+ }
297
+ if (c.origin !== origin) return null;
298
+ if (!isIdentityKey(opts.identityKey)) return null;
299
+ const identityKey = opts.identityKey.toLowerCase();
300
+ if (c.identityKey !== identityKey) return null;
301
+ const sig = opts.signature;
302
+ if (!Array.isArray(sig) || sig.length < 8 || sig.length > 80 || !sig.every((b) => Number.isInteger(b) && b >= 0 && b <= 255)) {
303
+ return null;
304
+ }
305
+ try {
306
+ const { valid } = await new import_sdk.ProtoWallet("anyone").verifySignature({
307
+ protocolID: LOGIN_PROTOCOL,
308
+ keyID: opts.nonce,
309
+ counterparty: identityKey,
310
+ data: Array.from(new TextEncoder().encode(c.message)),
311
+ signature: sig
312
+ });
313
+ return valid ? identityKey : null;
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
318
+ function originAllowed(requestOrigin, origin) {
319
+ try {
320
+ return !!requestOrigin && requestOrigin === normalOrigin(origin);
321
+ } catch {
322
+ return false;
323
+ }
324
+ }
325
+
326
+ // src/next.ts
327
+ var json = (body2, status = 200, headers = {}) => new Response(JSON.stringify(body2), { status, headers: { "content-type": "application/json", "cache-control": "no-store", ...headers } });
328
+ async function body(req) {
329
+ try {
330
+ const b = await req.json();
331
+ return b && typeof b === "object" ? b : null;
332
+ } catch {
333
+ return null;
334
+ }
335
+ }
336
+ function createBwalletXAuth(o) {
337
+ if (!o.store) throw new Error("createBwalletXAuth needs a nonce store (redisStore, pgStore; memoryStore for dev)");
338
+ const cookieName = o.cookieName ?? "bwx_session";
339
+ const maxAge = o.maxAge ?? 7 * 24 * 3600;
340
+ const secure = o.secureCookie ?? o.origin.startsWith("https:");
341
+ const secret = () => {
342
+ const s = o.secret ?? (typeof process !== "undefined" ? process.env?.[o.secretEnv ?? "BWALLETX_SESSION_SECRET"] : void 0);
343
+ assertSecret(s);
344
+ return s;
345
+ };
346
+ const cookie = (v, age) => serializeCookie(v, { name: cookieName, maxAge: age, secure, sameSite: o.sameSite, domain: o.cookieDomain });
347
+ const badOrigin = (req) => !originAllowed(req.headers.get("origin"), o.origin);
348
+ const fromSource = async (source) => {
349
+ if (typeof source === "string") return readCookie(source, cookieName);
350
+ if (source instanceof Headers) return readCookie(source.get("cookie"), cookieName);
351
+ if (source && typeof source.headers?.get === "function") return readCookie(source.headers.get("cookie"), cookieName);
352
+ const { cookies } = await import("next/headers");
353
+ const jar = await cookies();
354
+ return jar.get(cookieName)?.value ?? null;
355
+ };
356
+ const auth = {
357
+ async challenge(req) {
358
+ secret();
359
+ if (badOrigin(req)) return json({ error: "origin not allowed" }, 403);
360
+ const b = await body(req);
361
+ try {
362
+ return json(await createChallenge({ origin: o.origin, identityKey: b?.identityKey, store: o.store }));
363
+ } catch (e) {
364
+ return json({ error: e.message }, 400);
365
+ }
366
+ },
367
+ async verify(req) {
368
+ const key = secret();
369
+ if (badOrigin(req)) return json({ error: "origin not allowed" }, 403);
370
+ const b = await body(req);
371
+ const identityKey = await verifySignIn({ origin: o.origin, identityKey: b?.identityKey, nonce: b?.nonce, signature: b?.signature, store: o.store });
372
+ if (!identityKey) return json({ error: "sign-in failed" }, 401);
373
+ let extra = {};
374
+ try {
375
+ const r = await o.onVerified?.(identityKey, req);
376
+ if (r === false) return json({ error: "not allowed" }, 403);
377
+ if (r) extra = r;
378
+ } catch (e) {
379
+ if (e instanceof IdentityLinkConflictError) return json({ error: e.message, code: e.code }, 409);
380
+ throw e;
381
+ }
382
+ const token = await signSession({ ...extra, sub: identityKey }, { secret: key, maxAge });
383
+ return json({ identityKey, uid: extra.uid, handle: extra.handle, paymail: extra.paymail }, 200, { "set-cookie": cookie(token, maxAge) });
384
+ },
385
+ async logout(req) {
386
+ if (badOrigin(req)) return json({ error: "origin not allowed" }, 403);
387
+ return json({ ok: true }, 200, { "set-cookie": cookie("", 0) });
388
+ },
389
+ async session(req) {
390
+ const s = await auth.getSession(req);
391
+ return s ? json({ identityKey: s.sub, uid: s.uid, handle: s.handle, paymail: s.paymail, exp: s.exp }) : json({ identityKey: null }, 200);
392
+ },
393
+ async getSession(source) {
394
+ const key = secret();
395
+ return verifySession(await fromSource(source), { secret: key });
396
+ },
397
+ async sessionCookie(claims) {
398
+ return cookie(await signSession(claims, { secret: secret(), maxAge }), maxAge);
399
+ },
400
+ clearCookie: () => cookie("", 0)
401
+ };
402
+ return auth;
403
+ }
404
+ // Annotate the CommonJS export names for ESM import in node:
405
+ 0 && (module.exports = {
406
+ IdentityLinkConflictError,
407
+ createBwalletXAuth,
408
+ linkIdentity,
409
+ memoryStore,
410
+ pgNonceTableSql,
411
+ pgStore,
412
+ redisStore,
413
+ signInOrLink
414
+ });
415
+ //# sourceMappingURL=next.cjs.map