@oxy.so/protocol 1.0.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/LICENSE +202 -0
- package/NOTICE +16 -0
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/cjs/chain/continuity.js +54 -0
- package/dist/cjs/chain/engine.js +34 -0
- package/dist/cjs/chain/recordStore.js +25 -0
- package/dist/cjs/chain/types.js +22 -0
- package/dist/cjs/chain/verify.js +82 -0
- package/dist/cjs/envelope/canonicalJson.js +107 -0
- package/dist/cjs/envelope/recordId.js +60 -0
- package/dist/cjs/envelope/sign.js +75 -0
- package/dist/cjs/envelope/signingInput.js +32 -0
- package/dist/cjs/identity/resolver.js +50 -0
- package/dist/cjs/index.js +71 -0
- package/dist/cjs/node/constants.js +85 -0
- package/dist/cjs/node/didWebResolver.js +126 -0
- package/dist/cjs/node/httpFetch.js +61 -0
- package/dist/cjs/node/index.js +71 -0
- package/dist/cjs/node/nodeApp.js +344 -0
- package/dist/cjs/node/nodeClient.js +204 -0
- package/dist/cjs/node/rateLimit.js +187 -0
- package/dist/cjs/node/verifyRecord.js +51 -0
- package/dist/cjs/platform/crypto.js +186 -0
- package/dist/cjs/platform/crypto.native.js +204 -0
- package/dist/cjs/platform/expoTypes.js +24 -0
- package/dist/cjs/platform/platform.js +33 -0
- package/dist/cjs/secp256k1.js +148 -0
- package/dist/cjs/transparency/checkpoint.js +79 -0
- package/dist/cjs/transparency/tree.js +197 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/dist/esm/chain/continuity.js +51 -0
- package/dist/esm/chain/engine.js +31 -0
- package/dist/esm/chain/recordStore.js +24 -0
- package/dist/esm/chain/types.js +19 -0
- package/dist/esm/chain/verify.js +78 -0
- package/dist/esm/envelope/canonicalJson.js +104 -0
- package/dist/esm/envelope/recordId.js +56 -0
- package/dist/esm/envelope/sign.js +69 -0
- package/dist/esm/envelope/signingInput.js +29 -0
- package/dist/esm/identity/resolver.js +47 -0
- package/dist/esm/index.js +36 -0
- package/dist/esm/node/constants.js +82 -0
- package/dist/esm/node/didWebResolver.js +122 -0
- package/dist/esm/node/httpFetch.js +55 -0
- package/dist/esm/node/index.js +28 -0
- package/dist/esm/node/nodeApp.js +336 -0
- package/dist/esm/node/nodeClient.js +198 -0
- package/dist/esm/node/rateLimit.js +182 -0
- package/dist/esm/node/verifyRecord.js +48 -0
- package/dist/esm/platform/crypto.js +145 -0
- package/dist/esm/platform/crypto.native.js +196 -0
- package/dist/esm/platform/expoTypes.js +23 -0
- package/dist/esm/platform/platform.js +29 -0
- package/dist/esm/secp256k1.js +137 -0
- package/dist/esm/transparency/checkpoint.js +73 -0
- package/dist/esm/transparency/tree.js +189 -0
- package/dist/types/.tsbuildinfo +1 -0
- package/dist/types/chain/continuity.d.ts +28 -0
- package/dist/types/chain/engine.d.ts +27 -0
- package/dist/types/chain/recordStore.d.ts +85 -0
- package/dist/types/chain/types.d.ts +79 -0
- package/dist/types/chain/verify.d.ts +45 -0
- package/dist/types/envelope/canonicalJson.d.ts +44 -0
- package/dist/types/envelope/recordId.d.ts +30 -0
- package/dist/types/envelope/sign.d.ts +47 -0
- package/dist/types/envelope/signingInput.d.ts +33 -0
- package/dist/types/identity/resolver.d.ts +67 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/node/constants.d.ts +80 -0
- package/dist/types/node/didWebResolver.d.ts +47 -0
- package/dist/types/node/httpFetch.d.ts +60 -0
- package/dist/types/node/index.d.ts +28 -0
- package/dist/types/node/nodeApp.d.ts +120 -0
- package/dist/types/node/nodeClient.d.ts +135 -0
- package/dist/types/node/rateLimit.d.ts +95 -0
- package/dist/types/node/verifyRecord.d.ts +41 -0
- package/dist/types/platform/crypto.d.ts +93 -0
- package/dist/types/platform/crypto.native.d.ts +77 -0
- package/dist/types/platform/expoTypes.d.ts +99 -0
- package/dist/types/platform/platform.d.ts +25 -0
- package/dist/types/secp256k1.d.ts +45 -0
- package/dist/types/transparency/checkpoint.d.ts +71 -0
- package/dist/types/transparency/tree.d.ts +135 -0
- package/package.json +157 -0
- package/src/__tests__/canonicalJson.test.ts +116 -0
- package/src/__tests__/chain.test.ts +279 -0
- package/src/__tests__/didWebResolver.test.ts +132 -0
- package/src/__tests__/envelope.test.ts +267 -0
- package/src/__tests__/nodeApp.test.ts +410 -0
- package/src/__tests__/nodeClient.test.ts +177 -0
- package/src/__tests__/nodeHarness.ts +151 -0
- package/src/__tests__/optionalNativePeers.test.ts +233 -0
- package/src/__tests__/rateLimit.test.ts +268 -0
- package/src/__tests__/runnerGuard.test.ts +85 -0
- package/src/__tests__/secp256k1.test.ts +118 -0
- package/src/__tests__/transparency.test.ts +353 -0
- package/src/chain/continuity.ts +59 -0
- package/src/chain/engine.ts +43 -0
- package/src/chain/recordStore.ts +98 -0
- package/src/chain/types.ts +85 -0
- package/src/chain/verify.ts +102 -0
- package/src/envelope/canonicalJson.ts +120 -0
- package/src/envelope/recordId.ts +63 -0
- package/src/envelope/sign.ts +86 -0
- package/src/envelope/signingInput.ts +48 -0
- package/src/identity/resolver.ts +90 -0
- package/src/index.ts +101 -0
- package/src/node/constants.ts +105 -0
- package/src/node/didWebResolver.ts +162 -0
- package/src/node/httpFetch.ts +88 -0
- package/src/node/index.ts +87 -0
- package/src/node/nodeApp.ts +471 -0
- package/src/node/nodeClient.ts +322 -0
- package/src/node/rateLimit.ts +233 -0
- package/src/node/verifyRecord.ts +60 -0
- package/src/platform/crypto.native.ts +251 -0
- package/src/platform/crypto.ts +172 -0
- package/src/platform/expoTypes.ts +99 -0
- package/src/platform/platform.ts +31 -0
- package/src/secp256k1.ts +207 -0
- package/src/transparency/checkpoint.ts +109 -0
- package/src/transparency/tree.ts +258 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createDidWebResolver` — a {@link VerificationMethodResolver} for arbitrary
|
|
3
|
+
* `did:web` subjects, backing the chain engine on multi-subject relay/ingest
|
|
4
|
+
* paths (where the signer is NOT a local account but any DID that publishes a
|
|
5
|
+
* DID document).
|
|
6
|
+
*
|
|
7
|
+
* It resolves a `did:web:<host>[:<path>]` subject to its `did.json`, fetched via
|
|
8
|
+
* an INJECTED {@link NodeFetch} — Oxy passes an adapter over `@oxy.so/core/server`
|
|
9
|
+
* `safeFetch` (so the SSRF/transport policy stays in core); a test passes a
|
|
10
|
+
* stub. The subject's current verification keys are read from the DID document's
|
|
11
|
+
* `verificationMethod[].publicKeyHex` (the active assertion methods), and the
|
|
12
|
+
* engine's `isAuthorizedKey` then applies the uniform self-issued rule.
|
|
13
|
+
*
|
|
14
|
+
* `did:web` → URL mapping (W3C did:web):
|
|
15
|
+
* - `did:web:example.com` → `https://example.com/.well-known/did.json`
|
|
16
|
+
* - `did:web:example.com:u:123` → `https://example.com/u/123/did.json`
|
|
17
|
+
* - a `%3A` in the host segment is decoded to `:` (an explicit port).
|
|
18
|
+
*/
|
|
19
|
+
import type { VerificationMethodResolver } from '../identity/resolver';
|
|
20
|
+
import { type NodeFetch } from './httpFetch';
|
|
21
|
+
/** Options for {@link createDidWebResolver}. */
|
|
22
|
+
export interface DidWebResolverOptions {
|
|
23
|
+
/** Time-to-first-byte deadline for the `did.json` fetch (ms). */
|
|
24
|
+
headersTimeoutMs?: number;
|
|
25
|
+
/** Redirect budget for the `did.json` fetch (each re-validated by the transport). */
|
|
26
|
+
maxRedirects?: number;
|
|
27
|
+
/** Bounded read ceiling for the fetched `did.json`. */
|
|
28
|
+
maxBytes?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Notified when a subject cannot be resolved (mapping/fetch/parse failure).
|
|
31
|
+
* `resolve` still returns `null` (the engine treats that as "no authorized
|
|
32
|
+
* key") — this hook gives the failure visibility without a silent catch.
|
|
33
|
+
*/
|
|
34
|
+
onError?: (err: unknown, subjectDid: string) => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Map a `did:web` DID to its `did.json` URL, or `null` when `did` is not a
|
|
38
|
+
* well-formed `did:web` identifier.
|
|
39
|
+
*/
|
|
40
|
+
export declare function didWebToUrl(did: string): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Build a {@link VerificationMethodResolver} that resolves `did:web` subjects via
|
|
43
|
+
* the injected `fetch`. Returns `null` for any subject that is not a `did:web`
|
|
44
|
+
* DID, whose `did.json` cannot be fetched, or whose document fails schema
|
|
45
|
+
* validation — the engine then treats the signer as unauthorized.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createDidWebResolver(fetch: NodeFetch, options?: DidWebResolverOptions): VerificationMethodResolver;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The injected HTTP transport the node {@link ./nodeClient.NodeClient} and the
|
|
3
|
+
* {@link ./didWebResolver} drive — plus the bounded-read helpers that make a
|
|
4
|
+
* response stream safe to consume.
|
|
5
|
+
*
|
|
6
|
+
* The protocol package is app-agnostic and MUST NOT depend on `@oxy.so/core`
|
|
7
|
+
* (core depends on protocol). So instead of importing `@oxy.so/core/server`'s
|
|
8
|
+
* `safeFetch` directly, the node client/resolver accept a {@link NodeFetch} —
|
|
9
|
+
* Oxy supplies an adapter over `safeFetch` (HTTPS-only, DNS-pinned, private-IP
|
|
10
|
+
* denylist, bounded redirects); a test supplies an in-process stub. Either way
|
|
11
|
+
* the SSRF/transport policy stays in the injected implementation, and the
|
|
12
|
+
* bounded body read (the cap that stops a malicious node streaming forever)
|
|
13
|
+
* stays here, close to the parsing.
|
|
14
|
+
*
|
|
15
|
+
* A Node `IncomingMessage` (what `safeFetch` returns) satisfies
|
|
16
|
+
* {@link NodeFetchResponse.body} directly — it is an `AsyncIterable<Buffer>`,
|
|
17
|
+
* and `Buffer` is a `Uint8Array`.
|
|
18
|
+
*/
|
|
19
|
+
/** Per-request options the client passes to the injected transport. */
|
|
20
|
+
export interface NodeFetchInit {
|
|
21
|
+
/** HTTP method (`GET` / `POST` / `PUT`). */
|
|
22
|
+
method: string;
|
|
23
|
+
/** Request headers. */
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
/** Request body (for `POST` / `PUT`); omitted for `GET`. */
|
|
26
|
+
body?: Uint8Array;
|
|
27
|
+
/** Time-to-first-byte deadline in milliseconds. */
|
|
28
|
+
headersTimeoutMs?: number;
|
|
29
|
+
/** Redirect budget (each hop re-validated by the implementation). */
|
|
30
|
+
maxRedirects?: number;
|
|
31
|
+
}
|
|
32
|
+
/** The streamed, non-redirect response the transport returns. */
|
|
33
|
+
export interface NodeFetchResponse {
|
|
34
|
+
/** HTTP status code. */
|
|
35
|
+
status: number;
|
|
36
|
+
/** Response headers (a Node `IncomingHttpHeaders` satisfies this). */
|
|
37
|
+
headers: Record<string, string | string[] | undefined>;
|
|
38
|
+
/** Async-iterable byte body — read with the bounded helpers below. */
|
|
39
|
+
body: AsyncIterable<Uint8Array>;
|
|
40
|
+
/** Release the underlying stream when a bounded read is cut short. */
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The injected transport. Oxy adapts `@oxy.so/core/server`'s `safeFetch` to this
|
|
45
|
+
* shape; tests pass an in-process stub.
|
|
46
|
+
*/
|
|
47
|
+
export type NodeFetch = (url: string, init: NodeFetchInit) => Promise<NodeFetchResponse>;
|
|
48
|
+
/** Thrown when a response body exceeds the caller's byte ceiling. */
|
|
49
|
+
export declare class ResponseTooLargeError extends Error {
|
|
50
|
+
readonly maxBytes: number;
|
|
51
|
+
constructor(maxBytes: number);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read a response body into a single buffer, aborting (and destroying the
|
|
55
|
+
* stream) the moment it exceeds `maxBytes`. The bound is the defence against a
|
|
56
|
+
* node that streams an unbounded body.
|
|
57
|
+
*/
|
|
58
|
+
export declare function readBoundedBytes(res: NodeFetchResponse, maxBytes: number): Promise<Buffer>;
|
|
59
|
+
/** Read a bounded response body and parse it as JSON. */
|
|
60
|
+
export declare function readBoundedJson(res: NodeFetchResponse, maxBytes: number): Promise<unknown>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@oxy.so/protocol/node` — the runnable node substrate.
|
|
3
|
+
*
|
|
4
|
+
* The Node-only half of the protocol: the Express app factory that backs any
|
|
5
|
+
* Oxy-protocol data node ({@link createNodeApp}), the HTTP {@link NodeClient}
|
|
6
|
+
* that drives a node's routes, the `did:web` verification-method resolver, the
|
|
7
|
+
* record verifier, and the node-protocol shape constants. A SEPARATE subpath
|
|
8
|
+
* from the package root so this Express/Node-only code never enters React
|
|
9
|
+
* Native / web bundles that import `@oxy.so/protocol`.
|
|
10
|
+
*
|
|
11
|
+
* Reused by `@oxy.so/node` (the runnable node), a future `mention-node` (an
|
|
12
|
+
* env-only deployment of the same base), and oxy-api's node sync (which drives
|
|
13
|
+
* a node via `NodeClient`).
|
|
14
|
+
*/
|
|
15
|
+
export { createNodeApp, BlobHashMismatchError } from './nodeApp';
|
|
16
|
+
export type { NodeApp, NodeAppDependencies, NodeAppConfig, NodeStoreLike, OwnerAuth, NodeLogger, } from './nodeApp';
|
|
17
|
+
export { createRateLimiter, DEFAULT_WRITE_RATE_LIMIT, DEFAULT_MAX_RATE_LIMIT_ENTRIES, } from './rateLimit';
|
|
18
|
+
export type { RateLimitConfig, RateLimiter } from './rateLimit';
|
|
19
|
+
export { verifyNodeRecordEnvelope } from './verifyRecord';
|
|
20
|
+
export type { NodeVerifyResult, NodeVerifyRejectionReason } from './verifyRecord';
|
|
21
|
+
export { NodeClient, NodeClientError, trimTrailingSlashes } from './nodeClient';
|
|
22
|
+
export type { NodeClientOptions, NodeHead, NodeLogPage, NodeWriteResult, NodeBlobPutResult, NodeBlobPinAuth, } from './nodeClient';
|
|
23
|
+
export { readBoundedBytes, readBoundedJson, ResponseTooLargeError } from './httpFetch';
|
|
24
|
+
export type { NodeFetch, NodeFetchInit, NodeFetchResponse } from './httpFetch';
|
|
25
|
+
export { createDidWebResolver, didWebToUrl } from './didWebResolver';
|
|
26
|
+
export type { DidWebResolverOptions } from './didWebResolver';
|
|
27
|
+
export { PROTOCOL_VERSION, DEFAULT_WELL_KNOWN_PATH, DEFAULT_SERVICE_TYPE, DEFAULT_APP_NAMESPACE, DEFAULT_PORT, DEFAULT_LOG_LIMIT, MAX_LOG_LIMIT, DEFAULT_MAX_BLOB_BYTES, MAX_SYNC_BATCH, JSON_BODY_LIMIT, OWNER_AUTH_HEADERS, OWNER_AUTH_MAX_AGE_MS, NODE_MODES, OWNER_ACTION_BLOB_PIN, SHA256_HEX, NODE_HEAD_PATH, NODE_LOG_PATH, NODE_RECORDS_PATH, NODE_SYNC_PUSH_PATH, NODE_BLOBS_PATH, DEFAULT_CLIENT_TIMEOUT_MS, DEFAULT_CLIENT_MAX_REDIRECTS, DEFAULT_HEAD_MAX_BYTES, DEFAULT_LOG_MAX_BYTES, DEFAULT_WRITE_RESPONSE_MAX_BYTES, DEFAULT_DID_DOC_MAX_BYTES, } from './constants';
|
|
28
|
+
export type { NodeMode } from './constants';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createNodeApp` — the app-agnostic Express factory for an Oxy-protocol data
|
|
3
|
+
* node. A node stores and serves ONE owner's append-only signed-record log
|
|
4
|
+
* (their "personal repo") plus the content-addressed blobs the records point at.
|
|
5
|
+
*
|
|
6
|
+
* This is the engine extracted from `@oxy.so/node` so the SAME code can back many
|
|
7
|
+
* app-node deployments (the Oxy identity node, a future Mention node) that
|
|
8
|
+
* differ only by ENV: the namespace they serve, their well-known manifest path,
|
|
9
|
+
* their advertised protocol id + service-type, and their owner key. Everything
|
|
10
|
+
* app-specific is INJECTED:
|
|
11
|
+
*
|
|
12
|
+
* - `store` — a {@link RecordStore} + {@link BlobStore} (the node's SQLite
|
|
13
|
+
* store, or a test stub). The node holds exactly one subject's
|
|
14
|
+
* repo, so the store keys a single global chain and ignores the
|
|
15
|
+
* subject argument; `createNodeApp` passes the node's own key as
|
|
16
|
+
* a stable sentinel.
|
|
17
|
+
* - `ownerAuth` — the single write authority. Records and blob pins are
|
|
18
|
+
* authorized against the node's configured owner key. This is
|
|
19
|
+
* injected (rather than importing `@oxy.so/core/server`) so the
|
|
20
|
+
* protocol package never depends on core.
|
|
21
|
+
* - `config` — the wire-shape knobs (well-known path, protocol id,
|
|
22
|
+
* service-type, mode, blob ceiling, collection allowlist).
|
|
23
|
+
* - `logger` — structured logging for the terminal error handler.
|
|
24
|
+
*
|
|
25
|
+
* Endpoints:
|
|
26
|
+
* - `GET <wellKnownPath>` — node identity + liveness (a probe target).
|
|
27
|
+
* - `GET /oxy/head` — chain head `{ seq, headRecordId, recordCount }`.
|
|
28
|
+
* - `GET /oxy/log` — ordered envelopes from a cursor (ingest).
|
|
29
|
+
* - `POST /records` — owner writes a single signed envelope.
|
|
30
|
+
* - `POST /sync/push` — owner pushes a batch of signed envelopes.
|
|
31
|
+
* - `GET /blobs/:hash` — serve a content-addressed blob.
|
|
32
|
+
* - `PUT /blobs/:hash` — owner pins a blob (signed-header auth).
|
|
33
|
+
* - `GET /health` — container liveness.
|
|
34
|
+
*/
|
|
35
|
+
import { type Express } from 'express';
|
|
36
|
+
import type { RecordStore, BlobStore } from '../chain/recordStore';
|
|
37
|
+
import { type RateLimitConfig } from './rateLimit';
|
|
38
|
+
/**
|
|
39
|
+
* The Express app returned by {@link createNodeApp}, augmented with a {@link stop}
|
|
40
|
+
* hook that releases the app's background resources (currently the rate-limiter's
|
|
41
|
+
* sweep timer). The node bootstrap calls it from the graceful-shutdown path.
|
|
42
|
+
*/
|
|
43
|
+
export interface NodeApp extends Express {
|
|
44
|
+
/**
|
|
45
|
+
* Release the app's background resources (the rate-limiter sweep timer).
|
|
46
|
+
* Idempotent and safe to call on shutdown; not required for process exit (the
|
|
47
|
+
* timer is `unref()`'d) but keeps long-lived test harnesses leak-free.
|
|
48
|
+
*/
|
|
49
|
+
stop(): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Thrown by a {@link BlobStore.putBlob} implementation when bytes do not hash to
|
|
53
|
+
* the supplied address. Defined here (rather than in `@oxy.so/node`) so the node
|
|
54
|
+
* app can map it to `hash_mismatch` without importing the store implementation.
|
|
55
|
+
*/
|
|
56
|
+
export declare class BlobHashMismatchError extends Error {
|
|
57
|
+
readonly expected: string;
|
|
58
|
+
readonly actual: string;
|
|
59
|
+
constructor(expected: string, actual: string);
|
|
60
|
+
}
|
|
61
|
+
/** The store a node app drives — the chain log plus the blob store. */
|
|
62
|
+
export type NodeStoreLike = RecordStore & BlobStore;
|
|
63
|
+
/**
|
|
64
|
+
* The owner authority for node writes (the single write principal). Injected so
|
|
65
|
+
* the protocol package stays free of `@oxy.so/core` — `@oxy.so/node` provides an
|
|
66
|
+
* implementation bound to its configured owner key.
|
|
67
|
+
*/
|
|
68
|
+
export interface OwnerAuth {
|
|
69
|
+
/** True iff `publicKey` is the node's configured owner key (constant-time). */
|
|
70
|
+
isOwnerKey(publicKey: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Verify an owner-signed authorization for a blob pin over `hash` (a fresh
|
|
73
|
+
* signed header proving control, since the body is raw bytes not an envelope).
|
|
74
|
+
*/
|
|
75
|
+
verifyBlobPin(hash: string, auth: {
|
|
76
|
+
publicKey: string;
|
|
77
|
+
signature: string;
|
|
78
|
+
timestamp: number;
|
|
79
|
+
}): Promise<boolean>;
|
|
80
|
+
}
|
|
81
|
+
/** The wire-shape configuration a node app advertises + enforces. */
|
|
82
|
+
export interface NodeAppConfig {
|
|
83
|
+
/** Path the liveness manifest is served at (e.g. `/.well-known/oxy-node.json`). */
|
|
84
|
+
readonly wellKnownPath: string;
|
|
85
|
+
/** Node-protocol id advertised as `version` in the manifest (e.g. `oxy-node/1`). */
|
|
86
|
+
readonly protocolId: string;
|
|
87
|
+
/** Service-type label advertised in the manifest (e.g. `OxyPersonalDataNode`). */
|
|
88
|
+
readonly serviceType: string;
|
|
89
|
+
/** Operating mode advertised in the manifest (`self-hosted` / `managed`). */
|
|
90
|
+
readonly mode: string;
|
|
91
|
+
/** The node's advertised public key (its single-chain subject sentinel). */
|
|
92
|
+
readonly nodePublicKey: string;
|
|
93
|
+
/** Upper bound on a single pinned blob, in bytes. */
|
|
94
|
+
readonly maxBlobBytes: number;
|
|
95
|
+
/**
|
|
96
|
+
* Collection allowlist. EMPTY = accept any collection (the existing Oxy node
|
|
97
|
+
* behaviour). NON-EMPTY = only these collections may be written (else
|
|
98
|
+
* `foreign_collection`) and the public log is filtered to them.
|
|
99
|
+
*/
|
|
100
|
+
readonly collections: readonly string[];
|
|
101
|
+
/**
|
|
102
|
+
* Per-IP rate budget for the owner-authorized WRITE routes (`POST /records`,
|
|
103
|
+
* `POST /sync/push`, `PUT /blobs/:hash`). Defence-in-depth on the
|
|
104
|
+
* unauthenticated edge, capping request rate BEFORE signature verification.
|
|
105
|
+
* Defaults to {@link DEFAULT_WRITE_RATE_LIMIT} (60/min) — generous for the
|
|
106
|
+
* single-writer model.
|
|
107
|
+
*/
|
|
108
|
+
readonly writeRateLimit?: RateLimitConfig;
|
|
109
|
+
}
|
|
110
|
+
/** Minimal structured logger (a pino `Logger` satisfies this structurally). */
|
|
111
|
+
export interface NodeLogger {
|
|
112
|
+
error(obj: object, msg?: string): void;
|
|
113
|
+
}
|
|
114
|
+
export interface NodeAppDependencies {
|
|
115
|
+
store: NodeStoreLike;
|
|
116
|
+
config: NodeAppConfig;
|
|
117
|
+
ownerAuth: OwnerAuth;
|
|
118
|
+
logger: NodeLogger;
|
|
119
|
+
}
|
|
120
|
+
export declare function createNodeApp(deps: NodeAppDependencies): NodeApp;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `NodeClient` — the HTTP client that drives an Oxy-protocol data node's
|
|
3
|
+
* routes (head / log / records / blobs). It is the OUTBOUND half of the node
|
|
4
|
+
* protocol: oxy-api uses it to PULL a user's chain back from their node; a
|
|
5
|
+
* future Mention backend (B3) uses it to drive a node + push records/blobs.
|
|
6
|
+
*
|
|
7
|
+
* The client is transport-agnostic — it takes an injected {@link NodeFetch} so
|
|
8
|
+
* the protocol package never depends on `@oxy.so/core`. Oxy supplies an adapter
|
|
9
|
+
* over `@oxy.so/core/server`'s `safeFetch` (HTTPS-only, DNS-pinned, private-IP
|
|
10
|
+
* denylist, bounded redirects); a test supplies an in-process stub. Every
|
|
11
|
+
* response body is read with a hard byte ceiling, so a node cannot stream an
|
|
12
|
+
* unbounded body into the caller.
|
|
13
|
+
*/
|
|
14
|
+
import type { SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
15
|
+
import { type NodeFetch } from './httpFetch';
|
|
16
|
+
/** The chain head a node reports at `GET /oxy/head`. */
|
|
17
|
+
export interface NodeHead {
|
|
18
|
+
seq: number | null;
|
|
19
|
+
headRecordId: string | null;
|
|
20
|
+
recordCount: number;
|
|
21
|
+
}
|
|
22
|
+
/** One ordered page of a node's log (`GET /oxy/log`). */
|
|
23
|
+
export interface NodeLogPage {
|
|
24
|
+
/**
|
|
25
|
+
* The raw log items, returned VERBATIM (not re-parsed) — the caller validates
|
|
26
|
+
* + verifies each against the envelope schema. Preserves the node's exact wire
|
|
27
|
+
* shape so a puller's own verification is the trust boundary.
|
|
28
|
+
*/
|
|
29
|
+
records: unknown[];
|
|
30
|
+
count: number;
|
|
31
|
+
head: {
|
|
32
|
+
seq: number;
|
|
33
|
+
headRecordId: string;
|
|
34
|
+
} | null;
|
|
35
|
+
}
|
|
36
|
+
/** Outcome of an owner write (`POST /records`). */
|
|
37
|
+
export interface NodeWriteResult {
|
|
38
|
+
recordId: string;
|
|
39
|
+
seq: number;
|
|
40
|
+
}
|
|
41
|
+
/** Outcome of an owner blob pin (`PUT /blobs/:hash`). */
|
|
42
|
+
export interface NodeBlobPutResult {
|
|
43
|
+
hash: string;
|
|
44
|
+
size: number;
|
|
45
|
+
}
|
|
46
|
+
/** Owner-signed authorization for a blob pin (caller signs; client sends headers). */
|
|
47
|
+
export interface NodeBlobPinAuth {
|
|
48
|
+
publicKey: string;
|
|
49
|
+
signature: string;
|
|
50
|
+
timestamp: number;
|
|
51
|
+
}
|
|
52
|
+
/** A non-2xx node response (or a node that returned a malformed body). */
|
|
53
|
+
export declare class NodeClientError extends Error {
|
|
54
|
+
readonly status?: number | undefined;
|
|
55
|
+
readonly reason?: string | undefined;
|
|
56
|
+
constructor(message: string, status?: number | undefined, reason?: string | undefined);
|
|
57
|
+
}
|
|
58
|
+
/** Construction options for a {@link NodeClient}. */
|
|
59
|
+
export interface NodeClientOptions {
|
|
60
|
+
/** The node's HTTPS base URL (no trailing slash). */
|
|
61
|
+
baseUrl: string;
|
|
62
|
+
/** The injected transport (an adapter over `safeFetch`, or a test stub). */
|
|
63
|
+
fetch: NodeFetch;
|
|
64
|
+
/** Time-to-first-byte deadline per request (ms). */
|
|
65
|
+
headersTimeoutMs?: number;
|
|
66
|
+
/** Redirect budget per request (each re-validated by the transport). */
|
|
67
|
+
maxRedirects?: number;
|
|
68
|
+
/** Bounded read ceiling for a `/oxy/head` response. */
|
|
69
|
+
headMaxBytes?: number;
|
|
70
|
+
/** Bounded read ceiling for a `/oxy/log` page response. */
|
|
71
|
+
logMaxBytes?: number;
|
|
72
|
+
/** Bounded read ceiling for a small write/JSON response. */
|
|
73
|
+
writeResponseMaxBytes?: number;
|
|
74
|
+
/** Bounded read ceiling for a fetched blob. */
|
|
75
|
+
blobMaxBytes?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Trim every trailing slash from a base URL in LINEAR time.
|
|
79
|
+
*
|
|
80
|
+
* Replaces an anchored-quantifier regex (`/\/+$/`) whose backtracking is a
|
|
81
|
+
* polynomial-ReDoS sink on a long all-slash input; a single-pass scan is O(n)
|
|
82
|
+
* with no ReDoS surface.
|
|
83
|
+
*/
|
|
84
|
+
export declare function trimTrailingSlashes(value: string): string;
|
|
85
|
+
export declare class NodeClient {
|
|
86
|
+
private readonly baseUrl;
|
|
87
|
+
private readonly fetch;
|
|
88
|
+
private readonly headersTimeoutMs;
|
|
89
|
+
private readonly maxRedirects;
|
|
90
|
+
private readonly headMaxBytes;
|
|
91
|
+
private readonly logMaxBytes;
|
|
92
|
+
private readonly writeResponseMaxBytes;
|
|
93
|
+
private readonly blobMaxBytes;
|
|
94
|
+
constructor(options: NodeClientOptions);
|
|
95
|
+
/** Base request options shared by every call (timeout + redirect budget). */
|
|
96
|
+
private init;
|
|
97
|
+
/** The node's current chain head. Throws {@link NodeClientError} on a non-2xx. */
|
|
98
|
+
head(): Promise<NodeHead>;
|
|
99
|
+
/**
|
|
100
|
+
* One ordered page of the node's log strictly after `sinceSeq` (pass `-1` from
|
|
101
|
+
* genesis), capped at `limit`. Throws {@link NodeClientError} on a non-2xx or a
|
|
102
|
+
* response missing the `records` array.
|
|
103
|
+
*/
|
|
104
|
+
log(sinceSeq: number, limit: number): Promise<NodeLogPage>;
|
|
105
|
+
/**
|
|
106
|
+
* Write a single owner-signed envelope (`POST /records`). Throws
|
|
107
|
+
* {@link NodeClientError} (carrying the node's `reason`) on any non-2xx — a
|
|
108
|
+
* chain rejection (`chain_gap`/`chain_fork`/`bad_seq`/`chain_conflict`) or an
|
|
109
|
+
* authorization failure.
|
|
110
|
+
*/
|
|
111
|
+
writeRecord(envelope: SignedRecordEnvelope): Promise<NodeWriteResult>;
|
|
112
|
+
/**
|
|
113
|
+
* Push a batch of owner-signed envelopes (`POST /sync/push`). Returns the
|
|
114
|
+
* node's per-item results. Throws {@link NodeClientError} only on a non-2xx
|
|
115
|
+
* batch-level failure (`invalid_batch` / `batch_too_large`).
|
|
116
|
+
*/
|
|
117
|
+
pushRecords(envelopes: SignedRecordEnvelope[]): Promise<{
|
|
118
|
+
accepted: number;
|
|
119
|
+
results: Array<{
|
|
120
|
+
ok: boolean;
|
|
121
|
+
recordId?: string;
|
|
122
|
+
seq?: number;
|
|
123
|
+
reason?: string;
|
|
124
|
+
}>;
|
|
125
|
+
}>;
|
|
126
|
+
/** Fetch a content-addressed blob. Returns `null` on a 404; throws on other non-2xx. */
|
|
127
|
+
getBlob(hash: string): Promise<Buffer | null>;
|
|
128
|
+
/**
|
|
129
|
+
* Pin a content-addressed blob with an owner-signed authorization
|
|
130
|
+
* (`PUT /blobs/:hash`). The caller signs the pin (it holds the owner key) and
|
|
131
|
+
* passes the resulting `{ publicKey, signature, timestamp }`; the client sets
|
|
132
|
+
* the owner-auth headers. Throws {@link NodeClientError} on a non-2xx.
|
|
133
|
+
*/
|
|
134
|
+
putBlob(hash: string, bytes: Uint8Array, auth: NodeBlobPinAuth): Promise<NodeBlobPutResult>;
|
|
135
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, dependency-free fixed-window per-IP rate limiter for the node app's
|
|
3
|
+
* owner-authorized write routes.
|
|
4
|
+
*
|
|
5
|
+
* The node is a single-writer model (only the owner key may write), so the
|
|
6
|
+
* limiter is a defence-in-depth budget on the unauthenticated edge — it caps the
|
|
7
|
+
* request rate BEFORE signature verification so a flood of bogus envelopes can't
|
|
8
|
+
* pin CPU on crypto. It is intentionally process-local (a single node serves one
|
|
9
|
+
* owner's repo); there is no shared store to coordinate.
|
|
10
|
+
*
|
|
11
|
+
* Fixed-window counting, one budget per client: each key gets `max` requests per
|
|
12
|
+
* `windowMs`, and the window resets lazily on the first request after it elapses.
|
|
13
|
+
* The key is a SALTED HASH of the client address, never the address itself — see
|
|
14
|
+
* {@link clientRateLimitKey}.
|
|
15
|
+
*
|
|
16
|
+
* Bounded memory (defence against a key-rotation DoS — spoofed IPs / many DIDs
|
|
17
|
+
* growing the map without limit → memory exhaustion):
|
|
18
|
+
* - An ACTIVE periodic sweep on an `unref()`'d interval deletes every entry
|
|
19
|
+
* whose window has fully elapsed, so keys that are never touched again do not
|
|
20
|
+
* leak forever (lazy expiry-on-access alone cannot reclaim them). The
|
|
21
|
+
* interval is `unref()`'d so it never keeps the node process alive, and
|
|
22
|
+
* {@link RateLimiter.stop} clears it for a clean lifecycle teardown.
|
|
23
|
+
* - A hard cap on the number of tracked keys ({@link RateLimitConfig.maxEntries})
|
|
24
|
+
* evicts the OLDEST window (insertion-order LRU) when exceeded — a synchronous
|
|
25
|
+
* backstop against a burst that arrives between sweeps.
|
|
26
|
+
*/
|
|
27
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
28
|
+
/**
|
|
29
|
+
* The rate-limit key for a request: a salted hash of the client address, or the
|
|
30
|
+
* `'unknown'` sentinel when Express resolved no address at all (a request whose
|
|
31
|
+
* address is unknown cannot be budgeted individually, so all of them share one
|
|
32
|
+
* bucket — the same behaviour this limiter has always had).
|
|
33
|
+
*
|
|
34
|
+
* Truncated to 96 bits, which keeps a tracked entry to a short string beside its
|
|
35
|
+
* two numbers (the memory-bounding rationale on {@link RateLimitConfig.maxEntries}
|
|
36
|
+
* assumes exactly that). At the 10 000-entry cap a collision — two clients
|
|
37
|
+
* sharing one budget — has probability around 10⁸/2⁹⁷, i.e. never.
|
|
38
|
+
*
|
|
39
|
+
* Residue, named rather than left implicit: the address is hashed VERBATIM, so an
|
|
40
|
+
* IPv6 client that rotates through its /64 still mints a fresh key per address,
|
|
41
|
+
* exactly as it did before this was hashed. oxy-api's `hashedIpKey` buckets IPv6
|
|
42
|
+
* to /56 first to close that; doing the same here is a rate-limiting change with
|
|
43
|
+
* its own reasoning (it makes a whole prefix share one budget) and is deliberately
|
|
44
|
+
* not folded into a privacy fix.
|
|
45
|
+
*
|
|
46
|
+
* Exported for {@link createRateLimiter}'s own tests, not part of
|
|
47
|
+
* `@oxy.so/protocol/node`'s public surface — it is not re-exported by the barrel.
|
|
48
|
+
*/
|
|
49
|
+
export declare function clientRateLimitKey(req: Request): string;
|
|
50
|
+
/** A request-rate budget: at most `max` requests per `windowMs`. */
|
|
51
|
+
export interface RateLimitConfig {
|
|
52
|
+
/** The rolling window length, in milliseconds. */
|
|
53
|
+
readonly windowMs: number;
|
|
54
|
+
/** The maximum number of requests permitted within one window. */
|
|
55
|
+
readonly max: number;
|
|
56
|
+
/**
|
|
57
|
+
* Hard cap on the number of distinct keys (client identifiers) tracked at
|
|
58
|
+
* once. When the map exceeds this, the oldest-inserted window is evicted as a
|
|
59
|
+
* synchronous backstop against a burst of distinct keys arriving between
|
|
60
|
+
* sweeps. Defaults to {@link DEFAULT_MAX_RATE_LIMIT_ENTRIES}.
|
|
61
|
+
*/
|
|
62
|
+
readonly maxEntries?: number;
|
|
63
|
+
}
|
|
64
|
+
/** Default budget for owner write routes (generous — single-writer model). */
|
|
65
|
+
export declare const DEFAULT_WRITE_RATE_LIMIT: RateLimitConfig;
|
|
66
|
+
/**
|
|
67
|
+
* Default hard cap on tracked keys. Sized so the map's worst-case footprint
|
|
68
|
+
* stays small (each entry is a short string key + two numbers) while never
|
|
69
|
+
* evicting a legitimately active key for the single-writer node — the owner
|
|
70
|
+
* drives traffic from a handful of IPs, far below this ceiling.
|
|
71
|
+
*/
|
|
72
|
+
export declare const DEFAULT_MAX_RATE_LIMIT_ENTRIES = 10000;
|
|
73
|
+
/**
|
|
74
|
+
* The Express middleware returned by {@link createRateLimiter}, carrying a
|
|
75
|
+
* {@link stop} hook so the owning app can clear the background sweep on shutdown.
|
|
76
|
+
*/
|
|
77
|
+
export interface RateLimiter {
|
|
78
|
+
(req: Request, res: Response, next: NextFunction): void;
|
|
79
|
+
/**
|
|
80
|
+
* Stop the background sweep timer. Idempotent. Called by the node app's
|
|
81
|
+
* graceful-shutdown path; not required for process exit (the timer is
|
|
82
|
+
* `unref()`'d) but keeps long-lived test harnesses leak-free.
|
|
83
|
+
*/
|
|
84
|
+
stop(): void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build an Express middleware enforcing a fixed-window per-client rate limit.
|
|
88
|
+
* Exceeding the budget responds `429 { error: 'rate_limited' }` and does not call
|
|
89
|
+
* `next`. The key is {@link clientRateLimitKey} — a salted hash of Express's
|
|
90
|
+
* resolved client IP, so no address is held in the tracked map.
|
|
91
|
+
*
|
|
92
|
+
* The returned middleware owns a background sweep timer; call {@link RateLimiter.stop}
|
|
93
|
+
* to release it (e.g. on app shutdown).
|
|
94
|
+
*/
|
|
95
|
+
export declare function createRateLimiter(config: RateLimitConfig): RateLimiter;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record verification for a data node — reuses the protocol envelope primitives
|
|
3
|
+
* so a record verifies on the node with the EXACT code Oxy uses. No crypto is
|
|
4
|
+
* re-implemented:
|
|
5
|
+
*
|
|
6
|
+
* - {@link verifyEnvelopeSignature} recomputes the canonical signing input (the
|
|
7
|
+
* bytes the signature covers) from the envelope's own fields and checks the
|
|
8
|
+
* secp256k1 DER signature against the envelope's embedded `publicKey`.
|
|
9
|
+
* - {@link computeRecordId} recomputes `recordId = sha256(signingInput)` — the
|
|
10
|
+
* content address used as the chain's `prev` pointer.
|
|
11
|
+
*
|
|
12
|
+
* The envelope shape is validated with the shared `signedRecordEnvelopeSchema`.
|
|
13
|
+
* A node is a v2 hash chain, so only v2 envelopes (carrying
|
|
14
|
+
* `seq`/`prev`/`collection`/`rkey`) are accepted; v1 singletons have no chain
|
|
15
|
+
* coordinates.
|
|
16
|
+
*
|
|
17
|
+
* Verification here proves the signature is internally consistent with the
|
|
18
|
+
* embedded `publicKey`. Whether that key is authorized for the node is the OWNER
|
|
19
|
+
* check (the injected owner-key authority) — on a node the authority is the
|
|
20
|
+
* configured owner public key, not a DID lookup.
|
|
21
|
+
*/
|
|
22
|
+
import { type SignedRecordEnvelope } from '@oxy.so/contracts';
|
|
23
|
+
/** Stable, machine-readable reasons an envelope can fail node verification. */
|
|
24
|
+
export type NodeVerifyRejectionReason = 'invalid_envelope' | 'not_v2' | 'bad_signature';
|
|
25
|
+
export type NodeVerifyResult = {
|
|
26
|
+
ok: true;
|
|
27
|
+
envelope: SignedRecordEnvelope;
|
|
28
|
+
recordId: string;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
reason: NodeVerifyRejectionReason;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Validate, signature-check, and content-address a candidate signed record for a
|
|
35
|
+
* v2 hash-chain node.
|
|
36
|
+
*
|
|
37
|
+
* On success the parsed envelope and its `recordId` are returned; the caller
|
|
38
|
+
* (the node app) still enforces owner authority and chain continuity before the
|
|
39
|
+
* record is appended.
|
|
40
|
+
*/
|
|
41
|
+
export declare function verifyNodeRecordEnvelope(input: unknown): Promise<NodeVerifyResult>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Crypto / Storage — Default Variant (Node.js, Browser, generic bundlers)
|
|
3
|
+
*
|
|
4
|
+
* Provides lazy access to platform-specific crypto and storage modules.
|
|
5
|
+
*
|
|
6
|
+
* # Variants
|
|
7
|
+
*
|
|
8
|
+
* This module ships in two physical variants on disk, selected per consumer
|
|
9
|
+
* by the bundler / runtime:
|
|
10
|
+
*
|
|
11
|
+
* - `crypto.js` — this file. Used by Node.js, Vite, webpack,
|
|
12
|
+
* Rollup, esbuild, and anything that does not match
|
|
13
|
+
* Metro's `*.native.js` source-extension preference.
|
|
14
|
+
* - `crypto.native.js` — sibling file. Picked up automatically by Metro's
|
|
15
|
+
* resolver (which prefers `*.<platform>.js` and
|
|
16
|
+
* `*.native.js` over plain `*.js` when
|
|
17
|
+
* `preferNativePlatform` is true — Expo sets this for
|
|
18
|
+
* all non-web builds).
|
|
19
|
+
*
|
|
20
|
+
* The `package.json#exports` map also declares a `"react-native"` condition
|
|
21
|
+
* pointing at the same `dist/esm/index.js` entry — that entry transitively
|
|
22
|
+
* imports `./platform/crypto`, and Metro's per-file source-extension lookup
|
|
23
|
+
* substitutes the `.native.js` sibling automatically inside `dist/`. The
|
|
24
|
+
* package's top-level `"react-native"` map additionally pins the built
|
|
25
|
+
* `platform/crypto.js` (under both `dist/cjs` and `dist/esm`) to its
|
|
26
|
+
* `crypto.native.js` sibling belt-and-braces. This means consumers never have
|
|
27
|
+
* to add resolver shims; Metro Just Works.
|
|
28
|
+
*
|
|
29
|
+
* Both variants expose the EXACT same public API; importers don't need to know
|
|
30
|
+
* which one they got. The variant difference is purely about which underlying
|
|
31
|
+
* native modules each one references:
|
|
32
|
+
*
|
|
33
|
+
* ┌──────────────────┬───────────────────────┬───────────────────────────────┐
|
|
34
|
+
* │ Function │ Default variant │ React Native variant │
|
|
35
|
+
* ├──────────────────┼───────────────────────┼───────────────────────────────┤
|
|
36
|
+
* │ loadNodeCrypto │ `await import('crypto')` (Node built-in) │
|
|
37
|
+
* │ │ │ throws — Node crypto is not │
|
|
38
|
+
* │ │ │ available on Hermes/RN │
|
|
39
|
+
* ├──────────────────┼───────────────────────┼───────────────────────────────┤
|
|
40
|
+
* │ loadExpoCrypto │ throws — expo-crypto │ optional `require('expo- │
|
|
41
|
+
* │ │ is not part of a │ crypto')` │
|
|
42
|
+
* │ │ Node/Vite bundle │ │
|
|
43
|
+
* ├──────────────────┼───────────────────────┼───────────────────────────────┤
|
|
44
|
+
* │ loadSecureStore │ throws (web/Node have │ optional `require('expo- │
|
|
45
|
+
* │ │ their own storage) │ secure-store')` │
|
|
46
|
+
* ├──────────────────┼───────────────────────┼───────────────────────────────┤
|
|
47
|
+
* │ loadAsyncStorage │ throws (web/Node have │ optional `require('@react- │
|
|
48
|
+
* │ │ their own storage) │ native-async-storage/...')` │
|
|
49
|
+
* ├──────────────────┼───────────────────────┼───────────────────────────────┤
|
|
50
|
+
* │ getRandomBytesRN │ throws (RN-only) │ direct call into expo-crypto │
|
|
51
|
+
* └──────────────────┴───────────────────────┴───────────────────────────────┘
|
|
52
|
+
*
|
|
53
|
+
* Crucially, the default variant references ONLY Node's `'crypto'`. It never
|
|
54
|
+
* mentions `expo-*` or `@react-native-async-storage/*` — so Vite, webpack,
|
|
55
|
+
* esbuild, Rollup, and Node itself can bundle / require it without ever
|
|
56
|
+
* attempting to resolve those RN-only packages.
|
|
57
|
+
*
|
|
58
|
+
* The React Native variant references ONLY the RN packages, each behind
|
|
59
|
+
* Metro's optional-dependency mechanism (a literal `require()` inside a `try`)
|
|
60
|
+
* because they are declared OPTIONAL peer dependencies. It never mentions
|
|
61
|
+
* `'crypto'` — so Metro and Hermes have nothing to choke on.
|
|
62
|
+
*
|
|
63
|
+
* # Why not a single file with dynamic import?
|
|
64
|
+
*
|
|
65
|
+
* A previous iteration used a "bundler-opaque" `new Function('s', 'return
|
|
66
|
+
* import(s)')` trick so a single file could service every platform. It
|
|
67
|
+
* bundled cleanly on Metro but Hermes refused to PARSE the resulting
|
|
68
|
+
* `import()` expression inside a Function-constructor body
|
|
69
|
+
* (`SyntaxError: Invalid expression encountered` at the `(` of `import(`).
|
|
70
|
+
* The platform-extension split is the only approach that lets each runtime
|
|
71
|
+
* see a file containing only specifiers it can understand — no tricks, no
|
|
72
|
+
* runtime parsing risks.
|
|
73
|
+
*/
|
|
74
|
+
import type { ExpoCryptoLike, ExpoSecureStoreLike, SharedIdentityBridge } from './expoTypes';
|
|
75
|
+
export type { ExpoCryptoLike, ExpoSecureStoreLike, SharedIdentityBridge };
|
|
76
|
+
export declare function loadNodeCrypto(): Promise<typeof import('crypto')>;
|
|
77
|
+
export declare function loadExpoCrypto(): Promise<ExpoCryptoLike>;
|
|
78
|
+
export declare function loadSecureStore(): Promise<ExpoSecureStoreLike>;
|
|
79
|
+
export declare function loadAsyncStorage(): Promise<{
|
|
80
|
+
default: {
|
|
81
|
+
getItem: (key: string) => Promise<string | null>;
|
|
82
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
83
|
+
removeItem: (key: string) => Promise<void>;
|
|
84
|
+
};
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* Synchronous random-bytes via `expo-crypto.getRandomBytes`. Only available
|
|
88
|
+
* in the React Native variant. The default variant throws because Node and
|
|
89
|
+
* browsers have their own native CSPRNGs (`crypto.randomBytes` and
|
|
90
|
+
* `crypto.getRandomValues` respectively) — callers should use those.
|
|
91
|
+
*/
|
|
92
|
+
export declare function getRandomBytesRN(_byteCount: number): Uint8Array;
|
|
93
|
+
export declare function loadSharedIdentityBridge(): Promise<SharedIdentityBridge | null>;
|