From 548130ca6e4081c7c0e23711fd0ef6223c94d953 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 6 Dec 2023 16:35:03 +0700 Subject: [PATCH] feat: remove browserify, crypto-js, sourcemaps --- .vscode/settings.json | 4 ++ README.md | 28 ++++++++++++- examples/boostagram.js | 2 +- examples/decode-invoice.js | 2 +- examples/invoices.js | 2 +- examples/keysends.js | 2 +- examples/nwc/get-balance.js | 6 +-- examples/oauth2-public-callback_pkce_s256.mjs | 31 ++++++++++---- examples/send-to-ln-address.js | 2 +- examples/webhooks.js | 2 +- package.json | 7 +--- src/OAuth2User.ts | 41 +++++++++++++------ src/types.ts | 2 +- src/utils.ts | 4 ++ src/webln/OauthWeblnProvider.ts | 12 +++--- src/window.js | 3 -- tsconfig.json | 5 ++- yarn.lock | 10 ----- 18 files changed, 108 insertions(+), 57 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 src/window.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1b6457c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" +} diff --git a/README.md b/README.md index d1d8e08..db0be8d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,31 @@ This JavaScript SDK for the Alby OAuth2 Wallet API and the Nostr Wallet Connect npm install @getalby/sdk ``` +or + +``` +yarn add @getalby/sdk +``` + +or for use without any build tools: + +```html + +``` + **This library relies on a global fetch() function which will work in browsers and node v18.x or newer.** (In older versions you have to use a polyfill.) ## Content @@ -127,6 +152,7 @@ catch (e) { ``` #### React Native (Expo) + Look at our [NWC React Native Expo Demo app](https://github.com/getAlby/nwc-react-native-expo) for how to use NWC in a React Native expo project. #### For Node.js @@ -249,7 +275,7 @@ const authClient = new auth.OAuth2User({ }, // initialize with existing token }); -const authUrl = authClient.generateAuthURL({ +const authUrl = await authClient.generateAuthURL({ code_challenge_method: "S256", // authorizeUrl: "https://getalby.com/oauth" endpoint for authorization (replace with the appropriate URL based on the environment) }); diff --git a/examples/boostagram.js b/examples/boostagram.js index 70fd57d..1ce3712 100644 --- a/examples/boostagram.js +++ b/examples/boostagram.js @@ -25,7 +25,7 @@ const authClient = new auth.OAuth2User({ }); console.log(`Open the following URL and authenticate the app:`); -console.log(authClient.generateAuthURL()); +console.log(await authClient.generateAuthURL()); console.log("----\n"); const code = await rl.question("Code: (localhost:8080?code=[THIS CODE]: "); diff --git a/examples/decode-invoice.js b/examples/decode-invoice.js index e2771aa..2109d51 100644 --- a/examples/decode-invoice.js +++ b/examples/decode-invoice.js @@ -20,7 +20,7 @@ const authClient = new auth.OAuth2User({ }); console.log(`Open the following URL and authenticate the app:`); -console.log(authClient.generateAuthURL()); +console.log(await authClient.generateAuthURL()); console.log("----\n"); const code = await rl.question("Code: (localhost:8080?code=[THIS CODE]: "); diff --git a/examples/invoices.js b/examples/invoices.js index 03ef621..0e4c25c 100644 --- a/examples/invoices.js +++ b/examples/invoices.js @@ -18,7 +18,7 @@ const authClient = new auth.OAuth2User({ }); console.log(`Open the following URL and authenticate the app:`); -console.log(authClient.generateAuthURL()); +console.log(await authClient.generateAuthURL()); console.log("----\n"); const code = await rl.question("Code: (localhost:8080?code=[THIS CODE]: "); diff --git a/examples/keysends.js b/examples/keysends.js index 4db7783..fd76078 100644 --- a/examples/keysends.js +++ b/examples/keysends.js @@ -25,7 +25,7 @@ const authClient = new auth.OAuth2User({ }); console.log(`Open the following URL and authenticate the app:`); -console.log(authClient.generateAuthURL()); +console.log(await authClient.generateAuthURL()); console.log("----\n"); const code = await rl.question("Code: (localhost:8080?code=[THIS CODE]: "); diff --git a/examples/nwc/get-balance.js b/examples/nwc/get-balance.js index 48e45d1..1e77cd3 100644 --- a/examples/nwc/get-balance.js +++ b/examples/nwc/get-balance.js @@ -9,9 +9,9 @@ import { webln as providers } from "../../dist/index.module.js"; const rl = readline.createInterface({ input, output }); -const nwcUrl = await rl.question( - "Nostr Wallet Connect URL (nostr+walletconnect://...): ", -); +const nwcUrl = + process.env.NWC_URL || + (await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): ")); rl.close(); const webln = new providers.NostrWebLNProvider({ diff --git a/examples/oauth2-public-callback_pkce_s256.mjs b/examples/oauth2-public-callback_pkce_s256.mjs index 7311c8d..f91c941 100644 --- a/examples/oauth2-public-callback_pkce_s256.mjs +++ b/examples/oauth2-public-callback_pkce_s256.mjs @@ -1,14 +1,29 @@ import { auth, Client } from "../dist/index.module.js"; import express from "express"; +if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) { + throw new Error("Please set CLIENT_ID and CLIENT_SECRET"); +} + const app = express(); const authClient = new auth.OAuth2User({ client_id: process.env.CLIENT_ID, client_secret: process.env.CLIENT_SECRET, callback: "http://localhost:8080/callback", - scopes: ["invoices:read", "account:read", "balance:read", "invoices:create", "invoices:read", "payments:send"], - token: {access_token: undefined, refresh_token: undefined, expires_at: undefined} // initialize with existing token + scopes: [ + "invoices:read", + "account:read", + "balance:read", + "invoices:create", + "invoices:read", + "payments:send", + ], + token: { + access_token: undefined, + refresh_token: undefined, + expires_at: undefined, + }, // initialize with existing token }); const client = new Client(authClient); @@ -29,7 +44,7 @@ app.get("/callback", async function (req, res) { }); app.get("/login", async function (req, res) { - const authUrl = authClient.generateAuthURL({ + const authUrl = await authClient.generateAuthURL({ state: STATE, code_challenge_method: "S256", }); @@ -52,20 +67,20 @@ app.get("/value4value", async function (req, res) { }); app.get("/make-invoice", async function (req, res) { - const result = await client.createInvoice({amount: 1000}); + const result = await client.createInvoice({ amount: 1000 }); res.send(result); }); -app.get("/bolt11/:invoice", async function(req, res) { - const result = await client.sendPayment({invoice: req.params.invoice}); +app.get("/bolt11/:invoice", async function (req, res) { + const result = await client.sendPayment({ invoice: req.params.invoice }); res.send(result); }); -app.get('/keysend/:destination', async function(req, res) { +app.get("/keysend/:destination", async function (req, res) { const result = await client.keysend({ destination: req.params.destination, amount: 10, - memo: req.query.memo + memo: req.query.memo, }); res.send(result); }); diff --git a/examples/send-to-ln-address.js b/examples/send-to-ln-address.js index 568f5e3..307b46e 100644 --- a/examples/send-to-ln-address.js +++ b/examples/send-to-ln-address.js @@ -19,7 +19,7 @@ const authClient = new auth.OAuth2User({ }); console.log(`Open the following URL and authenticate the app:`); -console.log(authClient.generateAuthURL()); +console.log(await authClient.generateAuthURL()); console.log("----\n"); const code = await rl.question("Code: (localhost:8080?code=[THIS CODE]: "); diff --git a/examples/webhooks.js b/examples/webhooks.js index e8f50d2..a2cf29a 100644 --- a/examples/webhooks.js +++ b/examples/webhooks.js @@ -25,7 +25,7 @@ const authClient = new auth.OAuth2User({ }); console.log(`Open the following URL and authenticate the app:`); -console.log(authClient.generateAuthURL()); +console.log(await authClient.generateAuthURL()); console.log("----\n"); const code = await rl.question("Code: (localhost:8080?code=[THIS CODE]: "); diff --git a/package.json b/package.json index 43955f1..f1aabda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@getalby/sdk", - "version": "2.7.0", + "version": "3.0.0", "description": "The SDK to integrate with Nostr Wallet Connect and the Alby API", "repository": "https://github.com/getAlby/js-sdk.git", "bugs": "https://github.com/getAlby/js-sdk/issues", @@ -33,20 +33,17 @@ "prepack": "yarn run build", "test": "jest", "clean": "rm -rf dist", - "build:browser": "cp src/window.js dist && browserify dist/window.js > dist/index.browser.js", - "build": "microbundle && yarn build:browser", + "build": "microbundle --no-sourcemap", "dev": "microbundle watch", "prepare": "husky install" }, "dependencies": { - "crypto-js": "^4.1.1", "nostr-tools": "^1.17.0", "events": "^3.3.0" }, "devDependencies": { "@commitlint/cli": "^17.7.1", "@commitlint/config-conventional": "^17.7.0", - "@types/crypto-js": "^4.1.1", "@types/jest": "^29.5.5", "@types/node": "^20.8.6", "@typescript-eslint/eslint-plugin": "^6.3.0", diff --git a/src/OAuth2User.ts b/src/OAuth2User.ts index be52dc4..6196a25 100644 --- a/src/OAuth2User.ts +++ b/src/OAuth2User.ts @@ -1,5 +1,4 @@ -import CryptoJS from "crypto-js"; -import { buildQueryString, basicAuthHeader } from "./utils"; +import { buildQueryString, basicAuthHeader, toHexString } from "./utils"; import { OAuthClient, AuthHeader, @@ -75,6 +74,7 @@ export class OAuth2User implements OAuthClient { if (this._refreshAccessTokenPromise) { return this._refreshAccessTokenPromise; } + // eslint-disable-next-line no-async-promise-executor this._refreshAccessTokenPromise = new Promise(async (resolve, reject) => { try { const refresh_token = this.token?.refresh_token; @@ -109,7 +109,7 @@ export class OAuth2User implements OAuthClient { resolve({ token }); this._tokenEvents.emit("tokenRefreshed", this.token); } catch (error) { - console.log(error); + console.error(error); reject(error); this._tokenEvents.emit("tokenRefreshFailed", error); } finally { @@ -140,7 +140,9 @@ export class OAuth2User implements OAuthClient { throw new Error("client_id is required"); } if (!client_secret && !code_verifier) { - throw new Error("either client_secret is required, or code should be generated using a challenge"); + throw new Error( + "either client_secret is required, or code should be generated using a challenge", + ); } if (!callback) { throw new Error("callback is required"); @@ -171,23 +173,16 @@ export class OAuth2User implements OAuthClient { return { token }; } - generateAuthURL(options?: GenerateAuthUrlOptions): string { + async generateAuthURL(options?: GenerateAuthUrlOptions): Promise { if (!options) { options = {}; } - console.log(options); const { client_id, callback, scopes } = this.options; if (!callback) throw new Error("callback required"); if (!scopes) throw new Error("scopes required"); let code_challenge_method; if (options.code_challenge_method === "S256") { - const code_verifier = CryptoJS.lib.WordArray.random(64); - this.code_verifier = code_verifier.toString(); - this.code_challenge = CryptoJS.SHA256(this.code_verifier) - .toString(CryptoJS.enc.Base64) - .replace(/\+/g, "-") - .replace(/\//g, "_") - .replace(/\=+$/, ""); + await this._generateS256Challenge(); code_challenge_method = "S256"; } else if ( options.code_challenge_method === "plain" && @@ -220,4 +215,24 @@ export class OAuth2User implements OAuthClient { Authorization: `Bearer ${this.token.access_token}`, }; } + + private async _generateS256Challenge() { + const codeVerifierBytes = crypto.getRandomValues(new Uint8Array(64)); + this.code_verifier = toHexString(codeVerifierBytes); + + // from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest + const hashBuffer = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(this.code_verifier), + ); + const hashArray = new Uint8Array(hashBuffer); + + // from https://stackoverflow.com/a/45313868 + // TODO: consider using Buffer.from(hashBuffer).toString("base64") in NodeJS + this.code_challenge = btoa(String.fromCharCode(...hashArray)) + // from https://gist.github.com/jhurliman/1250118?permalink_comment_id=3194799 + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); + } } diff --git a/src/types.ts b/src/types.ts index 3534627..160d0ec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,7 +47,7 @@ export type GenerateAuthUrlOptions = { authorizeUrl?: string } & ( export abstract class OAuthClient implements AuthClient { abstract token?: Token; - abstract generateAuthURL(options: GenerateAuthUrlOptions): string; + abstract generateAuthURL(options: GenerateAuthUrlOptions): Promise; abstract requestAccessToken(code?: string): Promise<{ token: Token }>; abstract getAuthHeader( url?: string, diff --git a/src/utils.ts b/src/utils.ts index 314fc19..eb1245a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,3 +12,7 @@ export function basicAuthHeader( ) { return `Basic ${btoa(`${client_id}:${client_secret}`)}`; } + +// from https://stackoverflow.com/a/50868276 +export const toHexString = (bytes: Uint8Array) => + bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); diff --git a/src/webln/OauthWeblnProvider.ts b/src/webln/OauthWeblnProvider.ts index 9230007..74318af 100644 --- a/src/webln/OauthWeblnProvider.ts +++ b/src/webln/OauthWeblnProvider.ts @@ -13,7 +13,7 @@ export class OauthWeblnProvider { client: Client; auth: OAuthClient; oauth: boolean; - subscribers: Record void>; + subscribers: Record void>; isExecuting: boolean; constructor(options: { auth: OAuthClient }) { @@ -28,7 +28,7 @@ export class OauthWeblnProvider { this.subscribers[name] = callback; } - notify(name: string, payload?: any) { + notify(name: string, payload?: unknown) { const callback = this.subscribers[name]; if (callback) { callback(payload); @@ -45,7 +45,7 @@ export class OauthWeblnProvider { if (isBrowser()) { try { this.isExecuting = true; - const result = await this.openAuthorization(); + await this.openAuthorization(); } finally { this.isExecuting = false; } @@ -123,12 +123,14 @@ export class OauthWeblnProvider { } } - openAuthorization() { + async openAuthorization() { const height = 700; const width = 600; const top = window.outerHeight / 2 + window.screenY - height / 2; const left = window.outerWidth / 2 + window.screenX - width / 2; - const url = this.auth.generateAuthURL({ code_challenge_method: "S256" }); + const url = await this.auth.generateAuthURL({ + code_challenge_method: "S256", + }); return new Promise((resolve, reject) => { const popup = window.open( diff --git a/src/window.js b/src/window.js deleted file mode 100644 index dd73d5a..0000000 --- a/src/window.js +++ /dev/null @@ -1,3 +0,0 @@ -// assign alby-sdk exports to global window object (for index.browser.js) -// @ts-ignore -window["albySDK"] = require("./index.cjs"); diff --git a/tsconfig.json b/tsconfig.json index 5e063b5..207ce4a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,12 +6,13 @@ "skipLibCheck": true, "checkJs": true, "allowJs": true, - "declarationMap": true, + "declarationMap": false, "declaration": true, "allowSyntheticDefaultImports": true, "target": "es2020", + "module": "ESNext", "rootDir": "./src", - "sourceMap": true, + "sourceMap": false, "moduleResolution": "node" }, "include": ["src/**/*"], diff --git a/yarn.lock b/yarn.lock index 3755a76..ddbbb40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2021,11 +2021,6 @@ dependencies: "@babel/types" "^7.20.7" -"@types/crypto-js@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" - integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== - "@types/estree@*": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" @@ -3256,11 +3251,6 @@ crypto-browserify@^3.0.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-js@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" - integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== - css-declaration-sorter@^6.3.0: version "6.3.1" resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec"