mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-11-09 22:09:06 -05:00
NIP-83: JavaScript Registry
This commit is contained in:
parent
6871b3b334
commit
1158921a12
120
83.md
Normal file
120
83.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
NIP-83
|
||||
======
|
||||
|
||||
JavaScript Registry
|
||||
-------------------
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
JavaScript source files may be stored by relays, and then imported into web browsers or development environments.
|
||||
|
||||
This NIP defines two kinds:
|
||||
|
||||
- `8394` - JavaScript source file.
|
||||
- `8395` - TypeScript source file.
|
||||
|
||||
In both cases, the `content` field contains the source code of the file.
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": 8394,
|
||||
"id": "c17cf2f43580ad8238703ea32fb55c90c93402ca1f7d38085381f32c0f00e459",
|
||||
"pubkey": "c5dce01ee61fc62f62e2a825e2d598a839653b3175e0dcc072b1fe3c885f84a7",
|
||||
"created_at": 1709848074,
|
||||
"tags": [],
|
||||
"content": "/**\n * Infinite async generator. Iterates messages pushed to it until closed.\n * Only one consumer is expected to use a Machina instance at a time.\n *\n * @example\n * ```ts\n * // Create the Machina instance\n * const machina = new Machina<string>();\n *\n * // Async generator loop\n * async function getMessages() {\n * for await (const msg of machina.stream()) {\n * console.log(msg);\n * }\n * }\n *\n * // Start the generator\n * getMessages();\n *\n * // Push messages to it\n * machina.push('hello!');\n * machina.push('whats up?');\n * machina.push('greetings');\n * ```\n */\nexport class Machina {\n #queue = [];\n #resolve;\n #aborted = false;\n\n constructor(signal) {\n if (signal?.aborted) {\n this.abort();\n } else {\n signal?.addEventListener('abort', () => this.abort(), { once: true });\n }\n }\n\n /** Get messages as an AsyncGenerator. */\n async *[Symbol.asyncIterator]() {\n while (!this.#aborted) {\n if (this.#queue.length) {\n yield this.#queue.shift();\n continue;\n }\n\n await new Promise((_resolve) => {\n this.#resolve = _resolve;\n });\n }\n\n throw new DOMException('The signal has been aborted', 'AbortError');\n }\n\n /** Push a message into the Machina instance, making it available to the consumer of `stream()`. */\n push(data) {\n this.#queue.push(data);\n this.#resolve?.();\n }\n\n /** Stops streaming and throws an error to the consumer. */\n abort() {\n this.#aborted = true;\n this.#resolve?.();\n }\n}\n",
|
||||
"sig": "f644529abdf7ea12c570800847ccc337201fe6c47ea8e09902017e04d6f87605c4c3ab7cece1189df9271a27df06e0da0c6f35375098004644d4dfbc38ba78e7"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": 8395,
|
||||
"id": "3047567edd9d74f694c648850fef128963973379be6a42f49d40c90524fbc079",
|
||||
"pubkey": "c5dce01ee61fc62f62e2a825e2d598a839653b3175e0dcc072b1fe3c885f84a7",
|
||||
"created_at": 1709847680,
|
||||
"tags": [],
|
||||
"content": "/**\n * Infinite async generator. Iterates messages pushed to it until closed.\n * Only one consumer is expected to use a Machina instance at a time.\n *\n * @example\n * ```ts\n * // Create the Machina instance\n * const machina = new Machina<string>();\n *\n * // Async generator loop\n * async function getMessages() {\n * for await (const msg of machina.stream()) {\n * console.log(msg);\n * }\n * }\n *\n * // Start the generator\n * getMessages();\n *\n * // Push messages to it\n * machina.push('hello!');\n * machina.push('whats up?');\n * machina.push('greetings');\n * ```\n */\nexport class Machina<T> {\n #queue: T[] = [];\n #resolve: (() => void) | undefined;\n #aborted = false;\n\n constructor(signal?: AbortSignal) {\n if (signal?.aborted) {\n this.abort();\n } else {\n signal?.addEventListener('abort', () => this.abort(), { once: true });\n }\n }\n\n /** Get messages as an AsyncGenerator. */\n async *[Symbol.asyncIterator](): AsyncGenerator<T> {\n while (!this.#aborted) {\n if (this.#queue.length) {\n yield this.#queue.shift() as T;\n continue;\n }\n\n await new Promise<void>((_resolve) => {\n this.#resolve = _resolve;\n });\n }\n\n throw new DOMException('The signal has been aborted', 'AbortError');\n }\n\n /** Push a message into the Machina instance, making it available to the consumer of `stream()`. */\n push(data: T): void {\n this.#queue.push(data);\n this.#resolve?.();\n }\n\n /** Stops streaming and throws an error to the consumer. */\n private abort(): void {\n this.#aborted = true;\n this.#resolve?.();\n }\n}\n",
|
||||
"sig": "b01ab6e2273bc33594dde5634c68add95c6273b8a864bf8f4c8a85564db3e3df2c9981a3c5d8694cc4fa0e0f984635d6b51d5ca352eff96b7cb789c39429d303"
|
||||
}
|
||||
```
|
||||
|
||||
## Immutability
|
||||
|
||||
Source code events are considered immutable, and should NOT be deleted by supported clients or relays in response to kind `5` deletion requests.
|
||||
Relays may still remove content for any reason.
|
||||
|
||||
Relays can indicate support for immutability by adding this NIP to their `supported_nips` field.
|
||||
|
||||
## Gateway
|
||||
|
||||
It is possible to import JavaScript modules in supported runtimes using an HTTP Nostr gateway:
|
||||
|
||||
```
|
||||
https://<host>/<nip19>
|
||||
```
|
||||
|
||||
A gateway will:
|
||||
|
||||
- Try to look up the event (or else return 404).
|
||||
- Check that the event is of kind `8394` or `8395` (or else return 4xx).
|
||||
- Return the `content` field of the event as the response body.
|
||||
- Set an appropriate `Content-Type` header on the response.
|
||||
|
||||
### Usage with web browsers
|
||||
|
||||
Web browsers can use script tags to import JavaScript modules from a gateway:
|
||||
|
||||
```html
|
||||
<script type="module" src="https://gateway.tld/note1xpr4vlkan460d9xxfzzslmcj393ewvmehe4y9ayagrys2f8mcpus9rk8kj"></script>
|
||||
```
|
||||
|
||||
It is also possible to use module imports within the script tag:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { Machina } from 'https://gateway.tld/note1xpr4vlkan460d9xxfzzslmcj393ewvmehe4y9ayagrys2f8mcpus9rk8kj';
|
||||
</script>
|
||||
```
|
||||
|
||||
See [JavaScript modules on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) for more details.
|
||||
|
||||
### Usage with Deno
|
||||
|
||||
Like web browsers, Deno can import modules from a gateway using URLs:
|
||||
|
||||
```js
|
||||
import { Machina } from 'https://gateway.tld/note1xpr4vlkan460d9xxfzzslmcj393ewvmehe4y9ayagrys2f8mcpus9rk8kj';
|
||||
```
|
||||
|
||||
### Import maps
|
||||
|
||||
Import maps are supported by both [Deno](https://docs.deno.com/runtime/manual/basics/import_maps) and [web browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#importing_modules_using_import_maps), enabling us to configure the Nostr HTTP gateway once, and then use Nostr identifiers within our code:
|
||||
|
||||
```json
|
||||
{
|
||||
"imports": {
|
||||
"nostr/": "https://gateway.tld/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Our code becomes:
|
||||
|
||||
```js
|
||||
import { Machina } from 'nostr/note1xpr4vlkan460d9xxfzzslmcj393ewvmehe4y9ayagrys2f8mcpus9rk8kj';
|
||||
```
|
||||
|
||||
Now if a gateway goes offline, we can switch to a different one by changing only the import map.
|
||||
|
||||
## JavaScript formats
|
||||
|
||||
Kind `8394` events may be JavaScript modules (with import/export statements) or IIFE (immediately-invoked function expression) scripts.
|
||||
|
||||
Kind `8395` events are TypeScript modules.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Modules may depend on other modules. In this case, import maps are NOT optional.
|
||||
All imports must either be absolute URLs, or they must use the `nostr/` prefix and be resolved using an import map.
|
||||
The user agent must resolve the import map before attempting to fetch the module.
|
Loading…
Reference in New Issue
Block a user