From e797a6994e04fdfe9c8c82fe472b326ec904d6c0 Mon Sep 17 00:00:00 2001
From: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Date: Sat, 14 Jan 2023 19:34:21 +0100
Subject: [PATCH] disconnect from App (#2)
* disconnect from App
* example
---
README.md | 8 ++--
example/index.tsx | 100 ++++++++++++++++++++++++++-----------------
example/package.json | 2 +-
example/yarn.lock | 13 ++----
src/connect.ts | 39 +++++++++++++----
src/nostr.ts | 22 +++-------
test/connect.test.ts | 20 ++++++---
7 files changed, 117 insertions(+), 87 deletions(-)
diff --git a/README.md b/README.md
index 414f272..7bd1705 100644
--- a/README.md
+++ b/README.md
@@ -127,9 +127,7 @@ class MobileHandler extends NostrSigner {
### 📱 Create a MobileHandler instance
-Generate a key to identify the remote signer, it is used to be reached by the apps.
-
-An instance that extends `NostrSigner` has access to `isConnected(pubkey:string): boolean` method that tells you if the current request is from an app that has been granted the connection by the current signer app.
+Generate a key to identify the remote signer, it is used to be reached by the apps. At the moment it's your duty to persist locally a list of the apps that are allowed to connect to your remote signer.
```typescript
// random key
@@ -141,9 +139,9 @@ const handler = new MobileHandler({ secretKey });
remoteHandler.events.on('sign_event_request',
(event: Event) => {
// ⚠️⚠️⚠️ IMPORTANT: always check if the app is connected
- if (!remoteHandler.isConnected(event.pubkey)) return;
+
// do your UI stuff here to ask the user to approve or reject the request
-
+
// UI components can accept the sign
//this.events.emit('sign_event_approve');
diff --git a/example/index.tsx b/example/index.tsx
index 863a48a..535a471 100644
--- a/example/index.tsx
+++ b/example/index.tsx
@@ -33,11 +33,9 @@ const App = () => {
target,
});
connect.events.on('connect', (pubkey: string) => {
- console.log('We are connected to ' + pubkey)
setPubkey(pubkey);
});
connect.events.on('disconnect', () => {
- console.log('We got disconnected')
setEvent({});
setPubkey('');
setGetPublicKeyReply('');
@@ -58,56 +56,79 @@ const App = () => {
}
const sendMessage = async () => {
- if (pubkey.length === 0) return;
+ try {
+ if (pubkey.length === 0) return;
- const connect = new Connect({
- secretKey,
- target: pubkey,
- });
+ const connect = new Connect({
+ secretKey,
+ target: pubkey,
+ });
- let event: Event = {
- kind: 1,
- pubkey: pubkey,
- created_at: Math.floor(Date.now() / 1000),
- tags: [],
- content: "Running Nostr Connect 🔌"
- };
- event.id = getEventHash(event)
- event.sig = await connect.signEvent(event);
- const relay = await connectToRelay('wss://relay.damus.io');
- await broadcastToRelay(relay, event);
+ let event: Event = {
+ kind: 1,
+ pubkey: pubkey,
+ created_at: Math.floor(Date.now() / 1000),
+ tags: [],
+ content: "Running Nostr Connect 🔌"
+ };
+ event.id = getEventHash(event)
+ event.sig = await connect.signEvent(event);
+ const relay = await connectToRelay('wss://relay.damus.io');
+ await broadcastToRelay(relay, event);
+
+ setEvent(event);
+ } catch (error) {
+ console.error(error);
+ }
- setEvent(event);
}
const isConnected = () => {
return pubkey.length > 0;
}
- const copyToClipboard = () => {
- navigator.clipboard.writeText(connectURI.toString()).then(function() {
- alert('Copied!');
- }, function(err) {
- console.error('Async: Could not copy text: ', err);
+ const disconnect = async () => {
+ const connect = new Connect({
+ secretKey,
+ target: pubkey,
});
+ await connect.disconnect();
+ //cleanup
+ setEvent({});
+ setPubkey('');
+ setGetPublicKeyReply('');
+ }
+
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText(connectURI.toString()).then(undefined,
+ function (err) {
+ console.error('Async: Could not copy text: ', err);
+ });
}
return (
- <>
+
-
Nostr Connect Playground
+ Nostr Connect Playground
-
Nostr ID {getPublicKey(secretKey)}
+
Nostr ID {getPublicKey(secretKey)}
-
Status {isConnected() ? '🟢 Connected' : '🔴 Disconnected'}
+
Status {isConnected() ? '🟢 Connected' : '🔴 Disconnected'}
+ {
+ isConnected() &&
+ }
{!isConnected() &&
-
+
Connect with Nostr
-
+
{
Copy to clipboard
-
}
-
-
+ }
{
isConnected() &&
- <>
+
-
Get Public Key
-
-
Send a message with text Running Nostr Connect 🔌
+ Post a message on Damus relay with text Running Nostr Connect 🔌
- Send message to Nostr
+ Sign Event
{
Object.keys(eventWithSig).length > 0 &&
@@ -152,10 +172,10 @@ const App = () => {
/>
}
- >
+
}
- >
+
)
diff --git a/example/package.json b/example/package.json
index 07216af..82c65bc 100644
--- a/example/package.json
+++ b/example/package.json
@@ -8,7 +8,7 @@
"build": "parcel build --public-url /connect index.html"
},
"dependencies": {
- "@nostr-connect/connect": "^0.1.3",
+ "@nostr-connect/connect": "@nostr-connect/connect",
"nostr-tools": "^1.1.1",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
diff --git a/example/yarn.lock b/example/yarn.lock
index a05b05a..52ced4c 100644
--- a/example/yarn.lock
+++ b/example/yarn.lock
@@ -159,13 +159,8 @@
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1"
integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==
-"@nostr-connect/connect@^0.1.3":
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/@nostr-connect/connect/-/connect-0.1.3.tgz#427d43cd799fd253009aab490573aba698774b7f"
- integrity sha512-8BEolEbzWngU4CAGmWfHPxbnyAMXSR2hlww4da3a5NzGlySu22m/fxpy5ZuFybIbOgyerjY26KOuN67xx4Ai4A==
- dependencies:
- events "^3.3.0"
- nostr-tools "^1.0.1"
+"@nostr-connect/connect@file:../src":
+ version "0.0.0"
"@parcel/bundler-default@2.8.2":
version "2.8.2"
@@ -1047,7 +1042,7 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
-events@^3.1.0, events@^3.3.0:
+events@^3.1.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@@ -1268,7 +1263,7 @@ node-releases@^2.0.6:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae"
integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==
-nostr-tools@^1.0.1, nostr-tools@^1.1.1:
+nostr-tools@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.1.1.tgz#2be4cd650bc0a4d20650b6cf46fee451c9f565b8"
integrity sha512-mxgjbHR6nx2ACBNa2tBpeM/glsPWqxHPT1Kszx/XfzL+kUdi1Gm3Xz1UcaODQ2F84IFtCKNLO+aF31ZfTAhSYQ==
diff --git a/src/connect.ts b/src/connect.ts
index c915f62..c9043ac 100644
--- a/src/connect.ts
+++ b/src/connect.ts
@@ -18,8 +18,7 @@ export class ConnectURI {
static fromURI(uri: string): ConnectURI {
const url = new URL(uri);
const target = url.hostname || url.pathname.substring(2);
- if (!target)
- throw new Error('Invalid connect URI: missing target');
+ if (!target) throw new Error('Invalid connect URI: missing target');
const relay = url.searchParams.get('relay');
if (!relay) {
throw new Error('Invalid connect URI: missing relay');
@@ -53,9 +52,9 @@ export class ConnectURI {
}
toString() {
- return `nostrconnect://${this.target}?metadata=${JSON.stringify(
- this.metadata
- )}&relay=${this.relay}`;
+ return `nostrconnect://${this.target}?metadata=${encodeURIComponent(
+ JSON.stringify(this.metadata)
+ )}&relay=${encodeURIComponent(this.relay)}`;
}
async approve(secretKey: string): Promise {
@@ -71,7 +70,7 @@ export class ConnectURI {
params: [getPublicKey(secretKey)],
},
},
- { skipResponse: false }
+ { skipResponse: true }
);
}
@@ -135,7 +134,8 @@ export class Connect {
switch (payload.method) {
case 'connect': {
- if (!payload.params || payload.params.length !== 1) return;
+ if (!payload.params || payload.params.length !== 1)
+ throw new Error('connect: missing pubkey');
const [pubkey] = payload.params;
this.target = pubkey;
this.events.emit('connect', pubkey);
@@ -158,6 +158,30 @@ export class Connect {
this.events.off(evt, cb);
}
+ async disconnect(): Promise {
+ if (!this.target) throw new Error('Not connected');
+
+ // notify the UI that we are disconnecting
+ this.events.emit('disconnect');
+
+ try {
+ await this.rpc.call(
+ {
+ target: this.target,
+ request: {
+ method: 'disconnect',
+ params: [],
+ },
+ },
+ { skipResponse: true }
+ );
+ } catch (error) {
+ throw new Error('Failed to disconnect');
+ }
+
+ this.target = undefined;
+ }
+
async getPublicKey(): Promise {
if (!this.target) throw new Error('Not connected');
@@ -181,7 +205,6 @@ export class Connect {
params: [event],
},
});
- console.log('signature', signature);
return signature as string;
}
diff --git a/src/nostr.ts b/src/nostr.ts
index a2bba8a..f5bff58 100644
--- a/src/nostr.ts
+++ b/src/nostr.ts
@@ -1,23 +1,11 @@
import { NostrRPC } from './rpc';
export class NostrSigner extends NostrRPC {
- connectedAppIDs: string[];
- constructor(opts: { relay?: string | undefined; secretKey: string }) {
- super(opts);
- this.connectedAppIDs = [];
+ async disconnect(): Promise {
+ this.events.emit('disconnect');
+ return null;
}
-
- addConnectedApp = (pubkey: string) => {
- this.connectedAppIDs.push(pubkey);
- };
-
- removeConnectedApp = (pubkey: string) => {
- this.connectedAppIDs = this.connectedAppIDs.filter(
- (id: string) => id !== pubkey
- );
- };
-
- isConnected(pubkey: string): boolean {
- return this.connectedAppIDs.includes(pubkey);
+ isConnected(): boolean {
+ throw new Error('Method not implemented yet.');
}
}
diff --git a/test/connect.test.ts b/test/connect.test.ts
index 210128c..276a684 100644
--- a/test/connect.test.ts
+++ b/test/connect.test.ts
@@ -48,20 +48,27 @@ describe('Nostr Connect', () => {
relay: 'wss://nostr.vulpem.com',
metadata: {
name: 'Vulpem',
- description: 'Enabling the next generation of bitcoin-native financial services',
+ description:
+ 'Enabling the next generation of bitcoin-native financial services',
url: 'https://vulpem.com',
icons: ['https://vulpem.com/1000x860-p-500.422be1bc.png'],
},
});
const url = ConnectURI.fromURI(connectURI.toString());
- expect(url.target).toBe('b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4');
+ expect(url.target).toBe(
+ 'b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4'
+ );
expect(url.relay).toBe('wss://nostr.vulpem.com');
expect(url.metadata.name).toBe('Vulpem');
- expect(url.metadata.description).toBe('Enabling the next generation of bitcoin-native financial services');
+ expect(url.metadata.description).toBe(
+ 'Enabling the next generation of bitcoin-native financial services'
+ );
expect(url.metadata.url).toBe('https://vulpem.com');
expect(url.metadata.icons).toBeDefined();
- expect(url.metadata.icons!.length).toBe(1);
- expect(url.metadata.icons![0]).toBe('https://vulpem.com/1000x860-p-500.422be1bc.png');
+ expect(url.metadata.icons!.length).toBe(1);
+ expect(url.metadata.icons![0]).toBe(
+ 'https://vulpem.com/1000x860-p-500.422be1bc.png'
+ );
});
it.skip('connect', async () => {
const testHandler = jest.fn();
@@ -124,7 +131,7 @@ describe('Nostr Connect', () => {
remoteHandler.events.on('sign_event_request', (event: Event) => {
// ⚠️⚠️⚠️ IMPORTANT: always check if the app is connected
- if (!remoteHandler.isConnected(event.pubkey)) return;
+ //if (!remoteHandler.isConnected(event.pubkey)) return;
// assume user clicks on approve button on the UI
remoteHandler.events.emit('sign_event_approve');
});
@@ -186,7 +193,6 @@ describe('Nostr Connect', () => {
sessionWeb.on(ConnectMessageType.UNPAIRED, () => {
- console.log('unpaired');
});
*/