Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove browserify, crypto-js, sourcemaps #157

Merged
merged 1 commit into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<script type="module">
import { webln } from "https://esm.sh/@getalby/js-sdk@3.0.0"; // jsdelivr.net, skypack.dev also work

// use webln normally...
(async () => {
const nwc = new webln.NostrWebLNProvider({
nostrWalletConnectUrl: YOUR_NWC_URL,
});
await nwc.enable();
const balanceResponse = await nwc.getBalance();
console.log("Wallet balance", balanceResponse.balance);
nwc.close();
})();
</script>
```

**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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
});
Expand Down
2 changes: 1 addition & 1 deletion examples/boostagram.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ");
Expand Down
2 changes: 1 addition & 1 deletion examples/decode-invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ");
Expand Down
2 changes: 1 addition & 1 deletion examples/invoices.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ");
Expand Down
2 changes: 1 addition & 1 deletion examples/keysends.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ");
Expand Down
6 changes: 3 additions & 3 deletions examples/nwc/get-balance.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
31 changes: 23 additions & 8 deletions examples/oauth2-public-callback_pkce_s256.mjs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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",
});
Expand All @@ -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);
});
Expand Down
2 changes: 1 addition & 1 deletion examples/send-to-ln-address.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ");
Expand Down
2 changes: 1 addition & 1 deletion examples/webhooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]: ");
Expand Down
7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
41 changes: 28 additions & 13 deletions src/OAuth2User.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import CryptoJS from "crypto-js";
import { buildQueryString, basicAuthHeader } from "./utils";
import { buildQueryString, basicAuthHeader, toHexString } from "./utils";
import {
OAuthClient,
AuthHeader,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -171,23 +173,16 @@ export class OAuth2User implements OAuthClient {
return { token };
}

generateAuthURL(options?: GenerateAuthUrlOptions): string {
async generateAuthURL(options?: GenerateAuthUrlOptions): Promise<string> {
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" &&
Expand Down Expand Up @@ -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(/=+$/, "");
}
}
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
abstract requestAccessToken(code?: string): Promise<{ token: Token }>;
abstract getAuthHeader(
url?: string,
Expand Down
4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"), "");
12 changes: 7 additions & 5 deletions src/webln/OauthWeblnProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class OauthWeblnProvider {
client: Client;
auth: OAuthClient;
oauth: boolean;
subscribers: Record<string, (payload: any) => void>;
subscribers: Record<string, (payload: unknown) => void>;
isExecuting: boolean;

constructor(options: { auth: OAuthClient }) {
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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(
Expand Down
3 changes: 0 additions & 3 deletions src/window.js

This file was deleted.

5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/**/*"],
Expand Down
Loading