From 672b462bbed882b8be39f89b6e2d1c2003f90e1a Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sun, 10 Dec 2023 21:06:43 -0500 Subject: [PATCH] feat: add subscribeToEntry --- npm-shrinkwrap.json | 31 +++++++++++++---- package.json | 4 ++- src/client.ts | 34 +++---------------- src/methods/registry.ts | 73 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 38 deletions(-) create mode 100644 src/methods/registry.ts diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 6d0ecc2..11182b1 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8,14 +8,16 @@ "name": "@lumeweb/s5-js", "version": "0.1.0", "dependencies": { - "@lumeweb/libs5": "^0.1.0-develop.77", + "@lumeweb/libs5": "^0.1.0-develop.78", "@noble/hashes": "^1.3.2", "axios": "^1.6.2", + "isomorphic-ws": "^5.0.0", "tus-js-client": "^4.0.0", "url-join": "^5.0.0" }, "devDependencies": { "@lumeweb/node-library-preset": "^0.2.7", + "@types/ws": "^8.5.10", "presetter": "*" } }, @@ -1786,9 +1788,9 @@ } }, "node_modules/@lumeweb/libs5": { - "version": "0.1.0-develop.77", - "resolved": "https://registry.npmjs.org/@lumeweb/libs5/-/libs5-0.1.0-develop.77.tgz", - "integrity": "sha512-CDq5rhFFpLWouHIQCrc+VXO73ZXnIOOq3DoUzHJonqsUpQ2dBOZFJwfkumWVwlHIaEXHbfaRUrq6CCpE86L9vQ==", + "version": "0.1.0-develop.78", + "resolved": "https://registry.npmjs.org/@lumeweb/libs5/-/libs5-0.1.0-develop.78.tgz", + "integrity": "sha512-7ippBR7x9KbhkEjoMWPYZHJmmVxqeO2LvtAN+gJdj74NiAVujsKC1S3u5sXAeXyHdQ4Grsl8ClUfDumSirkdXg==", "dependencies": { "@noble/curves": "^1.1.0", "@noble/hashes": "^1.3.1", @@ -3513,7 +3515,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", "dev": true, - "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -3552,6 +3553,15 @@ "dev": true, "peer": true }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -9234,6 +9244,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/issue-parser": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", @@ -19658,8 +19676,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/unicorn-magic": { "version": "0.1.0", diff --git a/package.json b/package.json index 4d16b24..15743e3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "module": "lib/index.js", "devDependencies": { "@lumeweb/node-library-preset": "^0.2.7", + "@types/ws": "^8.5.10", "presetter": "*" }, "readme": "ERROR: No README data found!", @@ -15,9 +16,10 @@ "semantic-release": "semantic-release" }, "dependencies": { - "@lumeweb/libs5": "^0.1.0-develop.77", + "@lumeweb/libs5": "^0.1.0-develop.78", "@noble/hashes": "^1.3.2", "axios": "^1.6.2", + "isomorphic-ws": "^5.0.0", "tus-js-client": "^4.0.0", "url-join": "^5.0.0" }, diff --git a/src/client.ts b/src/client.ts index 3825e81..2aa3f1d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -24,6 +24,7 @@ import { ExecuteRequestError, Headers, } from "./request.js"; +import { subscribeToEntry } from "./methods/registry.js"; /** * Custom client options. @@ -125,6 +126,9 @@ export class S5Client { getCidUrl = getCidUrl; getMetadata = getMetadata; + // Registry + subscribeToEntry = subscribeToEntry; + /** * The S5 Client which can be used to access S5-net. * @@ -268,36 +272,6 @@ export class S5Client { } } - // =============== - // Private Methods - // =============== - - /** - * Gets the current server URL for the portal. You should generally use - * `portalUrl` instead - this method can be used for detecting whether the - * current URL is a server URL. - * - * @returns - The portal server URL. - */ - protected async resolvePortalServerUrl(): Promise { - const response = await this.executeRequest({ - ...this.customOptions, - method: "head", - url: this.initialPortalUrl, - }); - - if (!response.headers) { - throw new Error( - "Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists.", - ); - } - const portalUrl = response.headers["s5-server-api"]; - if (!portalUrl) { - throw new Error("Could not get server portal URL for the given portal"); - } - return portalUrl; - } - /** * Make a request to resolve the provided `initialPortalUrl`. * diff --git a/src/methods/registry.ts b/src/methods/registry.ts new file mode 100644 index 0000000..1cfa112 --- /dev/null +++ b/src/methods/registry.ts @@ -0,0 +1,73 @@ +import { DEFAULT_BASE_OPTIONS } from "../utils/options.js"; +import { CustomClientOptions, S5Client } from "../client.js"; +import { ensureBytes } from "@noble/curves/abstract/utils"; + +import WS from "isomorphic-ws"; +import { buildRequestUrl } from "#request.js"; +import { Packer, SignedRegistryEntry } from "@lumeweb/libs5"; +import { deserializeRegistryEntry } from "@lumeweb/libs5/lib/service/registry.js"; +import { Buffer } from "buffer"; +export const DEFAULT_GET_ENTRY_OPTIONS = { + ...DEFAULT_BASE_OPTIONS, + endpointGetEntry: "/s5/registry", +}; + +export const DEFAULT_SET_ENTRY_OPTIONS = { + ...DEFAULT_BASE_OPTIONS, + endpointSetEntry: "/s5/registry", + deleteForever: false, +}; + +export const DEFAULT_SUBSCRIBE_ENTRY_OPTIONS = { + ...DEFAULT_BASE_OPTIONS, + endpointSubscribeEntry: "/s5/registry/subscription", +}; + +export type BaseCustomOptions = CustomClientOptions; + +export type CustomRegistryOptions = BaseCustomOptions & { + endpointSubscribeEntry?: string; +}; + +export async function subscribeToEntry( + this: S5Client, + publicKey: Uint8Array, + customOptions?: CustomRegistryOptions, +) { + const opts = { + ...DEFAULT_SUBSCRIBE_ENTRY_OPTIONS, + ...this.customOptions, + ...customOptions, + }; + + publicKey = ensureBytes("public key", publicKey, 32); + + const url = await buildRequestUrl(this, { + baseUrl: await this.portalUrl(), + endpointPath: opts.endpointSubscribeEntry, + }); + + const socket = new WS(url); + + socket.once("open", () => { + const packer = new Packer(); + packer.pack(2); + packer.pack(publicKey); + + socket.send(packer.takeBytes()); + }); + + return { + listen(cb: (entry: SignedRegistryEntry) => void) { + socket.on("message", (data) => { + cb(deserializeRegistryEntry(new Uint8Array(data as Buffer))); + }); + }, + end() { + if ([socket.CLOSING, socket.CLOSED].includes(socket.readyState as any)) { + return; + } + socket.close(); + }, + }; +}