Compare commits

...

7 Commits

Author SHA1 Message Date
fiatjaf
896af30619 release v0.24.1 2022-09-06 15:19:29 -03:00
bjong
a8542c4b56 fix: CJK characters are garbled after decryption 2022-09-06 15:17:50 -03:00
fiatjaf
9f9e822c6d allow skipping signature verification. 2022-08-05 16:36:27 -03:00
Lennon Day-Reynolds
821a8f7895 TypeScript definitions (#18) 2022-07-15 15:49:49 -03:00
fiatjaf
2f7e3f8473 bump version. 2022-06-22 20:08:48 -03:00
monlovesmango
536dbcbffe Update pool.js 2022-06-22 20:07:25 -03:00
monlovesmango
ed52d2a8d4 updating cb property for subControllers entries
when updating subscription or adding new relays, subsequent events that are received have the relay as undefined. by updating cb property for the subControllers entries to be an arrow function (when calling sub.sub or sub.addRelay), subsequent events now return the relay url appropriately
2022-06-22 20:07:25 -03:00
12 changed files with 210 additions and 20 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ dist
yarn.lock
package-lock.json
nostr.js
.envrc

View File

@@ -84,6 +84,10 @@ You can import nostr-tools as an ES module. Just add a script tag like this:
And import whatever function you would import from `"nostr-tools"` in a bundler.
## TypeScript
This module has hand-authored TypeScript declarations. `npm run check-ts` will run a lint-check script to ensure the typings can be loaded and call at least a few standard library functions. It's not at all comprehensive and likely to contain bugs. Issues welcome; tag @rcoder as needed.
## License
Public domain.

View File

107
index.d.ts vendored Normal file
View File

@@ -0,0 +1,107 @@
import { type Buffer } from 'buffer';
// these should be available from the native @noble/secp256k1 type
// declarations, but they somehow aren't so instead: copypasta
declare type Hex = Uint8Array | string;
declare type PrivKey = Hex | bigint | number;
declare enum EventKind {
Metadata = 0,
Text = 1,
RelayRec = 2,
Contacts = 3,
DM = 4,
Deleted = 5,
}
// event.js
declare type Event = {
kind: EventKind,
pubkey?: string,
content: string,
tags: string[],
created_at: number,
};
declare function getBlankEvent(): Event;
declare function serializeEvent(event: Event): string;
declare function getEventHash(event: Event): string;
declare function validateEvent(event: Event): boolean;
declare function validateSignature(event: Event): boolean;
declare function signEvent(event: Event, key: PrivKey): Promise<[Uint8Array, number]>;
// filter.js
declare type Filter = {
ids: string[],
kinds: EventKind[],
authors: string[],
since: number,
until: number,
"#e": string[],
"#p": string[],
};
declare function matchFilter(filter: Filter, event: Event): boolean;
declare function matchFilters(filters: Filter[], event: Event): boolean;
// general
declare type ClientMessage =
["EVENT", Event] |
["REQ", string, Filter[]] |
["CLOSE", string];
declare type ServerMessage =
["EVENT", string, Event] |
["NOTICE", unknown];
// keys.js
declare function generatePrivateKey(): string;
declare function getPublicKey(privateKey: Buffer): string;
// pool.js
declare type RelayPolicy = {
read: boolean,
write: boolean,
};
declare type SubscriptionCallback = (event: Event, relay: string) => void;
declare type SubscriptionOptions = {
cb: SubscriptionCallback,
filter: Filter,
skipVerification: boolean
// TODO: thread through how `beforeSend` actually works before trying to type it
// beforeSend(event: Event):
};
declare type Subscription = {
unsub(): void,
};
declare type PublishCallback = (status: number) => void;
// relay.js
declare type Relay = {
url: string,
sub: SubscriptionCallback,
publish: (event: Event, cb: PublishCallback) => Promise<Event>,
};
declare type PoolPublishCallback = (status: number, relay: string) => void;
declare type RelayPool = {
setPrivateKey(key: string): void,
addRelay(url: string, opts?: RelayPolicy): Relay,
sub(opts: SubscriptionOptions, id?: string): Subscription,
publish(event: Event, cb: PoolPublishCallback): Promise<Event>,
close: () => void,
status: number,
};
declare function relayPool(): RelayPool;
// nip04.js
// nip05.js
// nip06.js

View File

@@ -1,6 +1,6 @@
import {generatePrivateKey, getPublicKey} from './keys'
import {relayConnect} from './relay'
import {relayPool} from './pool'
import {generatePrivateKey, getPublicKey} from './keys.js'
import {relayConnect} from './relay.js'
import {relayPool} from './pool.js'
import {
getBlankEvent,
signEvent,
@@ -8,8 +8,8 @@ import {
verifySignature,
serializeEvent,
getEventHash
} from './event'
import {matchFilter, matchFilters} from './filter'
} from './event.js'
import {matchFilter, matchFilters} from './filter.js'
export {
generatePrivateKey,

42
index.test-d.ts Normal file
View File

@@ -0,0 +1,42 @@
import * as process from 'process';
import {
relayPool,
getBlankEvent,
validateEvent,
RelayPool,
Event as NEvent
} from './index.js';
import { expectType } from 'tsd';
const pool = relayPool();
expectType<RelayPool>(pool);
const privkey = process.env.NOSTR_PRIVATE_KEY;
const pubkey = process.env.NOSTR_PUBLIC_KEY;
const message = {
...getBlankEvent(),
kind: 1,
content: `just saying hi from pid ${process.pid}`,
pubkey,
};
const publishCb = (status: number, url: string) => {
console.log({ status, url });
};
pool.setPrivateKey(privkey!);
const publishF = pool.publish(message, publishCb);
expectType<Promise<NEvent>>(publishF);
publishF.then((event) => {
expectType<NEvent>(event);
console.info({ event });
if (!validateEvent(event)) {
console.error(`event failed to validate!`);
process.exit(1);
}
});

View File

@@ -29,7 +29,7 @@ export function decrypt(privkey, pubkey, ciphertext) {
Buffer.from(normalizedKey, 'hex'),
Buffer.from(iv, 'base64')
)
let decryptedMessage = decipher.update(cip, 'base64')
let decryptedMessage = decipher.update(cip, 'base64', 'utf8')
decryptedMessage += decipher.final('utf8')
return decryptedMessage

View File

@@ -1,4 +1,4 @@
import {wordlist} from 'micro-bip39/wordlists/english'
import {wordlist} from 'micro-bip39/wordlists/english.js'
import {
generateMnemonic,
mnemonicToSeedSync,

View File

@@ -1,11 +1,12 @@
{
"name": "nostr-tools",
"version": "0.23.3",
"version": "0.24.1",
"description": "Tools for making a Nostr client.",
"repository": {
"type": "git",
"url": "https://github.com/fiatjaf/nostr-tools.git"
},
"type": "module",
"dependencies": {
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.5.2",
@@ -31,14 +32,18 @@
],
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
"@types/node": "^18.0.3",
"esbuild": "^0.14.38",
"esbuild-plugin-alias": "^0.2.1",
"eslint": "^8.5.0",
"eslint-plugin-babel": "^5.3.1",
"esm-loader-typescript": "^1.0.1",
"events": "^3.3.0",
"readable-stream": "^3.6.0"
"tsd": "^0.22.0",
"typescript": "^4.7.4"
},
"scripts": {
"prepublish": "node build.js"
"prepublish": "node build.cjs",
"check-ts": "tsd && node --no-warnings --loader=esm-loader-typescript index.test-d.ts"
}
}

13
pool.js
View File

@@ -1,5 +1,5 @@
import {getEventHash, verifySignature, signEvent} from './event'
import {relayConnect, normalizeRelayURL} from './relay'
import {getEventHash, verifySignature, signEvent} from './event.js'
import {relayConnect, normalizeRelayURL} from './relay.js'
export function relayPool() {
var globalPrivateKey
@@ -34,7 +34,7 @@ export function relayPool() {
.filter(({policy}) => policy.read)
.map(({relay}) => [
relay.url,
relay.sub({filter, cb: event => cb(event, relay.url), beforeSend}, id)
relay.sub({cb: event => cb(event, relay.url), filter, beforeSend}, id)
])
)
@@ -53,12 +53,15 @@ export function relayPool() {
}) => {
Object.entries(subControllers).map(([relayURL, sub]) => [
relayURL,
sub.sub({cb, filter, beforeSend}, id)
sub.sub({cb: event => cb(event, relayURL), filter, beforeSend}, id)
])
return activeSubscriptions[id]
}
const addRelay = relay => {
subControllers[relay.url] = relay.sub({cb, filter}, id)
subControllers[relay.url] = relay.sub(
{cb: event => cb(event, relay.url), filter, beforeSend},
id
)
return activeSubscriptions[id]
}
const removeRelay = relayURL => {

View File

@@ -2,8 +2,8 @@
import 'websocket-polyfill'
import {verifySignature, validateEvent} from './event'
import {matchFilters} from './filter'
import {verifySignature, validateEvent} from './event.js'
import {matchFilters} from './filter.js'
export function normalizeRelayURL(url) {
let [host, ...qs] = url.trim().split('?')
@@ -18,6 +18,7 @@ export function relayConnect(url, onNotice = () => {}, onError = () => {}) {
var ws, resolveOpen, untilOpen, wasClosed
var openSubs = {}
var isSetToSkipVerification = {}
let attemptNumber = 1
let nextAttemptSeconds = 1
@@ -94,7 +95,7 @@ export function relayConnect(url, onNotice = () => {}, onError = () => {}) {
if (
validateEvent(event) &&
verifySignature(event) &&
(isSetToSkipVerification[channel] || verifySignature(event)) &&
channels[channel] &&
matchFilters(openSubs[channel], event)
) {
@@ -120,7 +121,7 @@ export function relayConnect(url, onNotice = () => {}, onError = () => {}) {
}
const sub = (
{cb, filter, beforeSend},
{cb, filter, beforeSend, skipVerification},
channel = Math.random().toString().slice(2)
) => {
var filters = []
@@ -138,6 +139,7 @@ export function relayConnect(url, onNotice = () => {}, onError = () => {}) {
trySend(['REQ', channel, ...filters])
channels[channel] = cb
openSubs[channel] = filters
isSetToSkipVerification[channel] = skipVerification
const activeCallback = cb
const activeFilters = filters
@@ -148,10 +150,11 @@ export function relayConnect(url, onNotice = () => {}, onError = () => {}) {
cb = activeCallback,
filter = activeFilters,
beforeSend = activeBeforeSend
}) => sub({cb, filter, beforeSend}, channel),
}) => sub({cb, filter, beforeSend, skipVerification}, channel),
unsub: () => {
delete openSubs[channel]
delete channels[channel]
delete isSetToSkipVerification[channel]
trySend(['CLOSE', channel])
}
}

25
tsconfig.json Normal file
View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"module": "es2020",
"target": "es2020",
"lib": ["dom", "es2020"],
"esModuleInterop": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"declaration": true,
"strict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "./",
"typeRoots": ["."],
"types": ["node"],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"t/nostr-tools-tests.ts"
]
}