@toon-protocol/relay 2.0.1 → 2.1.0

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.js CHANGED
@@ -1,126 +1,79 @@
1
1
  import {
2
2
  ConnectionHandler,
3
+ DEFAULT_EPHEMERAL_MAX_BODY_BYTES,
4
+ DEFAULT_EPHEMERAL_RATE_LIMIT,
3
5
  DEFAULT_RELAY_CONFIG,
6
+ DELETION_KIND,
7
+ EXPIRATION_TAG,
4
8
  InMemoryEventStore,
5
9
  NostrRelayServer,
6
10
  RelayError,
7
11
  RelaySubscriber,
8
12
  SqliteEventStore,
9
13
  VERSION,
14
+ createEphemeralWriteHandler,
10
15
  createHealthResponse,
16
+ createMetricsRegistry,
17
+ createRateLimiter,
18
+ createVerifyPool,
11
19
  createWriteHandler,
20
+ defaultVerifyWorkers,
21
+ getExpiration,
22
+ isDeletableBy,
23
+ isDeletionKind,
24
+ isExpired,
25
+ isInternalBindHost,
12
26
  matchFilter,
13
- startRelay
14
- } from "./chunk-FXQSNOCG.js";
15
-
16
- // src/toon/codec.ts
17
- import { encode, decode } from "@toon-format/toon";
18
- var ToonEncodeError = class extends Error {
19
- code = "TOON_ENCODE_ERROR";
20
- constructor(message, cause) {
21
- super(message, { cause });
22
- this.name = "ToonEncodeError";
23
- }
24
- };
25
- var ToonDecodeError = class extends Error {
26
- code = "TOON_DECODE_ERROR";
27
- constructor(message, cause) {
28
- super(message, { cause });
29
- this.name = "ToonDecodeError";
30
- }
31
- };
32
- function encodeEventToToon(event) {
33
- return new TextEncoder().encode(encodeEventToToonString(event));
34
- }
35
- function encodeEventToToonString(event) {
36
- try {
37
- return encode(event);
38
- } catch (error) {
39
- throw new ToonEncodeError(
40
- `Failed to encode event to TOON: ${error instanceof Error ? error.message : String(error)}`,
41
- error instanceof Error ? error : void 0
42
- );
43
- }
44
- }
45
- function isValidHex(value, length) {
46
- return typeof value === "string" && value.length === length && /^[0-9a-f]+$/i.test(value);
47
- }
48
- function validateNostrEvent(obj) {
49
- if (typeof obj !== "object" || obj === null) {
50
- throw new ToonDecodeError("Decoded value is not an object");
51
- }
52
- const event = obj;
53
- if (!isValidHex(event["id"], 64)) {
54
- throw new ToonDecodeError(
55
- "Invalid event id: must be a 64-character hex string"
56
- );
57
- }
58
- if (!isValidHex(event["pubkey"], 64)) {
59
- throw new ToonDecodeError(
60
- "Invalid event pubkey: must be a 64-character hex string"
61
- );
62
- }
63
- if (typeof event["kind"] !== "number" || !Number.isInteger(event["kind"])) {
64
- throw new ToonDecodeError("Invalid event kind: must be an integer");
65
- }
66
- if (typeof event["content"] !== "string") {
67
- throw new ToonDecodeError("Invalid event content: must be a string");
68
- }
69
- const tags = event["tags"];
70
- if (!Array.isArray(tags)) {
71
- throw new ToonDecodeError("Invalid event tags: must be an array");
72
- }
73
- for (let i = 0; i < tags.length; i++) {
74
- const tag = tags[i];
75
- if (!Array.isArray(tag)) {
76
- throw new ToonDecodeError(`Invalid event tags[${i}]: must be an array`);
77
- }
78
- for (let j = 0; j < tag.length; j++) {
79
- if (typeof tag[j] !== "string") {
80
- throw new ToonDecodeError(
81
- `Invalid event tags[${i}][${j}]: must be a string`
82
- );
83
- }
84
- }
85
- }
86
- if (typeof event["created_at"] !== "number" || !Number.isInteger(event["created_at"])) {
87
- throw new ToonDecodeError("Invalid event created_at: must be an integer");
88
- }
89
- if (!isValidHex(event["sig"], 128)) {
90
- throw new ToonDecodeError(
91
- "Invalid event sig: must be a 128-character hex string"
92
- );
93
- }
94
- }
95
- function decodeEventFromToon(data) {
96
- let decoded;
97
- try {
98
- decoded = decode(new TextDecoder().decode(data));
99
- } catch (error) {
100
- throw new ToonDecodeError(
101
- `Failed to decode TOON data: ${error instanceof Error ? error.message : String(error)}`,
102
- error instanceof Error ? error : void 0
103
- );
104
- }
105
- validateNostrEvent(decoded);
106
- return decoded;
107
- }
27
+ parseAddressCoordinate,
28
+ parseBlockedEventIds,
29
+ parseDeletionTargets,
30
+ readOpenFilesSoftLimit,
31
+ readPaymentAttribution,
32
+ serializeEventFrame,
33
+ startRelay,
34
+ warnIfWritePortExposed
35
+ } from "./chunk-IZOSPMWV.js";
36
+ import {
37
+ verifyEventId,
38
+ verifyEventSignature,
39
+ verifyImplementation
40
+ } from "./chunk-SMT6G3XD.js";
108
41
  export {
109
42
  ConnectionHandler,
43
+ DEFAULT_EPHEMERAL_MAX_BODY_BYTES,
44
+ DEFAULT_EPHEMERAL_RATE_LIMIT,
110
45
  DEFAULT_RELAY_CONFIG,
46
+ DELETION_KIND,
47
+ EXPIRATION_TAG,
111
48
  InMemoryEventStore,
112
49
  NostrRelayServer,
113
50
  RelayError,
114
51
  RelaySubscriber,
115
52
  SqliteEventStore,
116
- ToonDecodeError,
117
- ToonEncodeError,
118
53
  VERSION,
54
+ createEphemeralWriteHandler,
119
55
  createHealthResponse,
56
+ createMetricsRegistry,
57
+ createRateLimiter,
58
+ createVerifyPool,
120
59
  createWriteHandler,
121
- decodeEventFromToon,
122
- encodeEventToToon,
60
+ defaultVerifyWorkers,
61
+ getExpiration,
62
+ isDeletableBy,
63
+ isDeletionKind,
64
+ isExpired,
65
+ isInternalBindHost,
123
66
  matchFilter,
124
- startRelay
67
+ parseAddressCoordinate,
68
+ parseBlockedEventIds,
69
+ parseDeletionTargets,
70
+ readOpenFilesSoftLimit,
71
+ readPaymentAttribution,
72
+ serializeEventFrame,
73
+ startRelay,
74
+ verifyEventId,
75
+ verifyEventSignature,
76
+ verifyImplementation,
77
+ warnIfWritePortExposed
125
78
  };
126
79
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/toon/codec.ts"],"sourcesContent":["/**\n * TOON event codec.\n *\n * Encodes/decodes Nostr events to and from the TOON text format. Vendored from\n * `@toon-protocol/core` so the relay depends only on the lightweight\n * `@toon-format/toon` encoder rather than core's full transitive tree (which\n * pulls Arweave / web3 wallet stacks the relay does not use). Same MIT\n * license / org.\n *\n * NOTE: this codec is NOT used on the relay's NIP-01 read surface. Outbound\n * EVENT frames are canonical NIP-01 JSON (see\n * `websocket/ConnectionHandler.sendEvent`, #46) so that standard nostr clients\n * can parse events and verify signatures from the wire. The codec remains\n * exported for library consumers that exchange TOON-text events elsewhere.\n *\n * @module\n */\n\nimport { encode, decode } from '@toon-format/toon';\nimport type { NostrEvent } from 'nostr-tools/pure';\n\n/** Thrown when a Nostr event cannot be encoded to TOON. */\nexport class ToonEncodeError extends Error {\n readonly code = 'TOON_ENCODE_ERROR';\n constructor(message: string, cause?: Error) {\n super(message, { cause });\n this.name = 'ToonEncodeError';\n }\n}\n\n/** Thrown when TOON data cannot be decoded into a valid Nostr event. */\nexport class ToonDecodeError extends Error {\n readonly code = 'TOON_DECODE_ERROR';\n constructor(message: string, cause?: Error) {\n super(message, { cause });\n this.name = 'ToonDecodeError';\n }\n}\n\n/** Encode a Nostr event to TOON bytes (UTF-8). */\nexport function encodeEventToToon(event: NostrEvent): Uint8Array {\n return new TextEncoder().encode(encodeEventToToonString(event));\n}\n\n/** Encode a Nostr event to a TOON string. */\nexport function encodeEventToToonString(event: NostrEvent): string {\n try {\n return encode(event);\n } catch (error) {\n throw new ToonEncodeError(\n `Failed to encode event to TOON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\nfunction isValidHex(value: unknown, length: number): boolean {\n return (\n typeof value === 'string' &&\n value.length === length &&\n /^[0-9a-f]+$/i.test(value)\n );\n}\n\nfunction validateNostrEvent(obj: unknown): asserts obj is NostrEvent {\n if (typeof obj !== 'object' || obj === null) {\n throw new ToonDecodeError('Decoded value is not an object');\n }\n const event = obj as Record<string, unknown>;\n if (!isValidHex(event['id'], 64)) {\n throw new ToonDecodeError(\n 'Invalid event id: must be a 64-character hex string'\n );\n }\n if (!isValidHex(event['pubkey'], 64)) {\n throw new ToonDecodeError(\n 'Invalid event pubkey: must be a 64-character hex string'\n );\n }\n if (typeof event['kind'] !== 'number' || !Number.isInteger(event['kind'])) {\n throw new ToonDecodeError('Invalid event kind: must be an integer');\n }\n if (typeof event['content'] !== 'string') {\n throw new ToonDecodeError('Invalid event content: must be a string');\n }\n const tags = event['tags'];\n if (!Array.isArray(tags)) {\n throw new ToonDecodeError('Invalid event tags: must be an array');\n }\n for (let i = 0; i < tags.length; i++) {\n const tag: unknown = tags[i];\n if (!Array.isArray(tag)) {\n throw new ToonDecodeError(`Invalid event tags[${i}]: must be an array`);\n }\n for (let j = 0; j < tag.length; j++) {\n if (typeof tag[j] !== 'string') {\n throw new ToonDecodeError(\n `Invalid event tags[${i}][${j}]: must be a string`\n );\n }\n }\n }\n if (\n typeof event['created_at'] !== 'number' ||\n !Number.isInteger(event['created_at'])\n ) {\n throw new ToonDecodeError('Invalid event created_at: must be an integer');\n }\n if (!isValidHex(event['sig'], 128)) {\n throw new ToonDecodeError(\n 'Invalid event sig: must be a 128-character hex string'\n );\n }\n}\n\n/** Decode TOON bytes into a validated Nostr event. */\nexport function decodeEventFromToon(data: Uint8Array): NostrEvent {\n let decoded: unknown;\n try {\n decoded = decode(new TextDecoder().decode(data));\n } catch (error) {\n throw new ToonDecodeError(\n `Failed to decode TOON data: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Error ? error : undefined\n );\n }\n validateNostrEvent(decoded);\n return decoded;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,17 @@
1
+ import {
2
+ verifyEventSignature
3
+ } from "./chunk-SMT6G3XD.js";
4
+
5
+ // src/crypto/verify-worker.ts
6
+ import { parentPort } from "worker_threads";
7
+ if (!parentPort) {
8
+ throw new Error("verify-worker must be started as a worker thread");
9
+ }
10
+ var port = parentPort;
11
+ port.on("message", (message) => {
12
+ port.postMessage({
13
+ seq: message.seq,
14
+ ok: verifyEventSignature(message.event)
15
+ });
16
+ });
17
+ //# sourceMappingURL=verify-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/crypto/verify-worker.ts"],"sourcesContent":["/**\n * Worker-thread entrypoint for the verify pool (relay#85).\n *\n * Each worker imports `verify-event.ts` in its own thread, so each worker\n * instantiates its OWN WASM libsecp256k1 module (with the same load-time\n * self-test and transparent noble fallback as the inline path -- WASM\n * instances are not shareable across threads anyway).\n *\n * Protocol: the parent posts `{ seq, event }`; the worker replies\n * `{ seq, ok }`. `verifyEventSignature` never throws, so every request gets\n * exactly one reply.\n *\n * Built as its own tsup entry (`dist/verify-worker.js`) because\n * `worker_threads` needs a real JS file on disk, not a bundled import.\n *\n * @module\n */\n\nimport { parentPort } from 'node:worker_threads';\nimport type { NostrEvent } from 'nostr-tools/pure';\nimport { verifyEventSignature } from './verify-event.js';\n\nif (!parentPort) {\n throw new Error('verify-worker must be started as a worker thread');\n}\n\nconst port = parentPort;\nport.on('message', (message: { seq: number; event: NostrEvent }) => {\n port.postMessage({\n seq: message.seq,\n ok: verifyEventSignature(message.event),\n });\n});\n"],"mappings":";;;;;AAkBA,SAAS,kBAAkB;AAI3B,IAAI,CAAC,YAAY;AACf,QAAM,IAAI,MAAM,kDAAkD;AACpE;AAEA,IAAM,OAAO;AACb,KAAK,GAAG,WAAW,CAAC,YAAgD;AAClE,OAAK,YAAY;AAAA,IACf,KAAK,QAAQ;AAAA,IACb,IAAI,qBAAqB,QAAQ,KAAK;AAAA,EACxC,CAAC;AACH,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@toon-protocol/relay",
3
- "version": "2.0.1",
4
- "description": "Nostr relay app: free NIP-01 reads + HTTP POST /write",
3
+ "version": "2.1.0",
4
+ "description": "Nostr relay app: free NIP-01 WebSocket reads + an HTTP POST /write surface",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -24,8 +24,12 @@
24
24
  "keywords": [
25
25
  "nostr",
26
26
  "relay",
27
- "ilp",
28
- "interledger"
27
+ "toon",
28
+ "nip-01",
29
+ "nip-09",
30
+ "nip-16",
31
+ "nip-40",
32
+ "websocket"
29
33
  ],
30
34
  "license": "MIT",
31
35
  "repository": {
@@ -39,10 +43,10 @@
39
43
  },
40
44
  "dependencies": {
41
45
  "@hono/node-server": "^1.0.0",
42
- "@toon-format/toon": "^1.0.0",
43
46
  "better-sqlite3": "^11.0.0",
44
47
  "hono": "^4.11.10",
45
48
  "nostr-tools": "^2.20.0",
49
+ "tiny-secp256k1": "^2.2.4",
46
50
  "ws": "^8.0.0"
47
51
  },
48
52
  "devDependencies": {