@solarpunkltd/swarm-id 0.0.0-stage → 0.8.1
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/README.md +89 -2
- package/dist/bus/account-bus.d.ts +104 -0
- package/dist/bus/account-delta.d.ts +52 -0
- package/dist/bus/bus-context.d.ts +7 -0
- package/dist/bus/envelope.d.ts +9 -0
- package/dist/bus/messages.d.ts +241 -0
- package/dist/bus/presence.d.ts +62 -0
- package/dist/bus/signaling-transport.d.ts +79 -0
- package/dist/chunk/bmt.d.ts +16 -0
- package/dist/chunk/cac.d.ts +17 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/encrypted-cac.d.ts +47 -0
- package/dist/chunk/encryption.d.ts +85 -0
- package/dist/chunk/index.d.ts +5 -0
- package/dist/chunks/chat-binding-DO5aGhPl.js +23 -0
- package/dist/chunks/chat-binding-DO5aGhPl.js.map +1 -0
- package/dist/errors.d.ts +35 -0
- package/dist/index.d.ts +27 -0
- package/dist/internal.d.ts +49 -0
- package/dist/internal.esm.js +2 -0
- package/dist/internal.esm.js.map +1 -0
- package/dist/proxy/act/act.d.ts +58 -0
- package/dist/proxy/act/crypto.d.ts +46 -0
- package/dist/proxy/act/grantee-list.d.ts +81 -0
- package/dist/proxy/act/history.d.ts +182 -0
- package/dist/proxy/act/index.d.ts +114 -0
- package/dist/proxy/app-stamp-limits.d.ts +62 -0
- package/dist/proxy/chunk-upload-stream.d.ts +37 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/collection.d.ts +38 -0
- package/dist/proxy/download-data.d.ts +27 -0
- package/dist/proxy/errors.d.ts +8 -0
- package/dist/proxy/feed-manifest.d.ts +60 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +70 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +79 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +60 -0
- package/dist/proxy/feeds/epochs/index.d.ts +30 -0
- package/dist/proxy/feeds/epochs/types.d.ts +110 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +76 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +21 -0
- package/dist/proxy/feeds/index.d.ts +4 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +11 -0
- package/dist/proxy/feeds/sequence/index.d.ts +22 -0
- package/dist/proxy/feeds/sequence/types.d.ts +82 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +20 -0
- package/dist/proxy/index.d.ts +5 -0
- package/dist/proxy/manifest-builder.d.ts +164 -0
- package/dist/proxy/mantaray.d.ts +76 -0
- package/dist/proxy/soc-restamp-slots.d.ts +48 -0
- package/dist/proxy/stamp-chunk.d.ts +45 -0
- package/dist/proxy/stamp-worker-handler.d.ts +32 -0
- package/dist/proxy/stamp-worker-pool.d.ts +52 -0
- package/dist/proxy/stamp-worker.d.ts +1 -0
- package/dist/proxy/types.d.ts +38 -0
- package/dist/proxy/upload.d.ts +207 -0
- package/dist/schemas.d.ts +671 -0
- package/dist/stamp-worker.iife.js +1 -0
- package/dist/storage/debounced-uploader.d.ts +61 -0
- package/dist/storage/utilization-store.d.ts +113 -0
- package/dist/swarm-id-client.d.ts +1143 -0
- package/dist/swarm-id-proxy.d.ts +1079 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/sync/batch-write-coordinator.d.ts +348 -0
- package/dist/sync/device-roster.d.ts +59 -0
- package/dist/sync/device-state.d.ts +199 -0
- package/dist/sync/fold-account-from-swarm.d.ts +53 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/merge-snapshot.d.ts +57 -0
- package/dist/sync/partition-intent.d.ts +218 -0
- package/dist/sync/partition-lease.d.ts +531 -0
- package/dist/sync/partition-lock.d.ts +218 -0
- package/dist/sync/partition-state.d.ts +287 -0
- package/dist/sync/session-check.d.ts +29 -0
- package/dist/sync/store-interfaces.d.ts +48 -0
- package/dist/sync/sync-account.d.ts +45 -0
- package/dist/sync/timing-constants.d.ts +32 -0
- package/dist/sync/types.d.ts +18 -0
- package/dist/types.d.ts +3042 -0
- package/dist/utils/account-state-snapshot.d.ts +43 -0
- package/dist/utils/active-devices.d.ts +45 -0
- package/dist/utils/avatar.d.ts +30 -0
- package/dist/utils/batch-utilization.d.ts +840 -0
- package/dist/utils/batch-write-lock.d.ts +12 -0
- package/dist/utils/bee-error.d.ts +11 -0
- package/dist/utils/chat-binding.d.ts +113 -0
- package/dist/utils/coalesced-task.d.ts +28 -0
- package/dist/utils/collection.d.ts +6 -0
- package/dist/utils/connection-info.d.ts +11 -0
- package/dist/utils/constants.d.ts +28 -0
- package/dist/utils/device-id.d.ts +109 -0
- package/dist/utils/hex.d.ts +31 -0
- package/dist/utils/json-rpc.d.ts +34 -0
- package/dist/utils/key-derivation.d.ts +124 -0
- package/dist/utils/lock-soc.d.ts +35 -0
- package/dist/utils/postage-contract.d.ts +143 -0
- package/dist/utils/postage-stamp-association.d.ts +39 -0
- package/dist/utils/promise.d.ts +37 -0
- package/dist/utils/sealed-values.d.ts +23 -0
- package/dist/utils/seed-encryption.d.ts +17 -0
- package/dist/utils/site-allow-list.d.ts +54 -0
- package/dist/utils/stamp-lifespan.d.ts +37 -0
- package/dist/utils/storage-managers.d.ts +62 -0
- package/dist/utils/storage-probe.d.ts +40 -0
- package/dist/utils/tag.d.ts +11 -0
- package/dist/utils/ttl.d.ts +120 -0
- package/dist/utils/url.d.ts +55 -0
- package/dist/utils/versioned-storage.d.ts +183 -0
- package/package.json +91 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `op` under an exclusive, cross-tab Web Lock keyed on the postage batch.
|
|
3
|
+
*
|
|
4
|
+
* All writers to a given batch — every same-origin context: dApp proxy iframes
|
|
5
|
+
* and the SwarmID UI — request the same lock name, so only one write runs at a
|
|
6
|
+
* time across tabs. This is the single definition of the `swarm-write-<batchId>`
|
|
7
|
+
* convention (the proxy and the account-sync path both use it).
|
|
8
|
+
*
|
|
9
|
+
* Falls back to running `op` directly when the Web Locks API is unavailable
|
|
10
|
+
* (Node / test environments).
|
|
11
|
+
*/
|
|
12
|
+
export declare function withBatchWriteLock<T>(batchIdHex: string | undefined, op: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True only for a definitive "chunk absent" — an HTTP 404, as Bee's `/soc`
|
|
3
|
+
* endpoint returns for a missing single-owner chunk (`BeeResponseError.status`).
|
|
4
|
+
* A timeout / 5xx / network error is INCONCLUSIVE, not a confirmed miss.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: Bee's `/chunks` endpoint returns **500** ("read chunk failed") for an
|
|
7
|
+
* absent chunk, so this predicate is usable for the roster `/soc` existence read
|
|
8
|
+
* but NOT for the `/chunks`-based feed finders — there, absence and a transient
|
|
9
|
+
* server error are indistinguishable by status.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isNotFoundError(error: unknown): boolean;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** The first line, which also versions the format. */
|
|
2
|
+
export declare const CHAT_BINDING_TITLE = "Swarm ID chat binding v1";
|
|
3
|
+
export interface SiteOriginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Also accept `http://localhost`, `http://127.0.0.1` and `http://[::1]`
|
|
6
|
+
* with any port. Only a development build passes this.
|
|
7
|
+
*/
|
|
8
|
+
allowLocalHttp?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** A signed binding as it travels: the message and its signature. */
|
|
11
|
+
export interface ChatBinding {
|
|
12
|
+
message: string;
|
|
13
|
+
/** `0x` + r ‖ s ‖ v, 65 bytes, v 27 or 28. */
|
|
14
|
+
signature: string;
|
|
15
|
+
}
|
|
16
|
+
/** The three fields a binding message carries, as the message spells them. */
|
|
17
|
+
export interface ChatBindingFields {
|
|
18
|
+
site: string;
|
|
19
|
+
/** `0x` + 40 lower-case hex characters. */
|
|
20
|
+
chatAddress: string;
|
|
21
|
+
/** ISO 8601 UTC, as `Date.prototype.toISOString` writes it. */
|
|
22
|
+
date: string;
|
|
23
|
+
}
|
|
24
|
+
export interface VerifiedChatBinding extends ChatBindingFields {
|
|
25
|
+
/** The recovered signer, `0x` + 40 lower-case hex characters. */
|
|
26
|
+
signer: string;
|
|
27
|
+
}
|
|
28
|
+
/** What a binding has to name for a reader to accept it. */
|
|
29
|
+
export interface ExpectedChatBinding {
|
|
30
|
+
site: string;
|
|
31
|
+
chatAddress: string;
|
|
32
|
+
signer: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether `value` is a bare https origin in the exact form the browser
|
|
36
|
+
* reports one: a host and nothing else, no path, query, fragment or user
|
|
37
|
+
* info, lower case, punycode, no default port. Anything a URL parser would
|
|
38
|
+
* have to normalize is refused rather than normalized, so one site has one
|
|
39
|
+
* spelling.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isCleanSiteOrigin(value: string, options?: SiteOriginOptions): boolean;
|
|
42
|
+
/** Build the exact four-line message for these fields. */
|
|
43
|
+
export declare function buildChatBindingMessage(fields: {
|
|
44
|
+
site: string;
|
|
45
|
+
chatAddress: string;
|
|
46
|
+
date: Date;
|
|
47
|
+
}, options?: SiteOriginOptions): string;
|
|
48
|
+
/**
|
|
49
|
+
* Read the fields out of a binding message, refusing every shape but the one
|
|
50
|
+
* `buildChatBindingMessage` writes: another first line, a missing or extra
|
|
51
|
+
* line, a trailing newline, a site that is not a clean origin, an address in
|
|
52
|
+
* another spelling, a date in another form.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseChatBindingMessage(message: string, options?: SiteOriginOptions): ChatBindingFields;
|
|
55
|
+
/**
|
|
56
|
+
* The chat address a site's binding names: the address of the site's chat key
|
|
57
|
+
* (`deriveChatKey`), bare lower-case hex. The one place that says which key a
|
|
58
|
+
* binding is about, for the window that signs one and the frame that checks a
|
|
59
|
+
* handed-over one.
|
|
60
|
+
*
|
|
61
|
+
* @param appSecret - the site's app secret (bare hex)
|
|
62
|
+
*/
|
|
63
|
+
export declare function siteChatAddress(appSecret: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Sign the binding for `site` with the account's own key. The chat address is
|
|
66
|
+
* the address of the site's chat key, derived from that same key here, so no
|
|
67
|
+
* caller can bind an address the account does not hold for that site.
|
|
68
|
+
*
|
|
69
|
+
* @param accountKey - the account's private key (bare hex), whose address is
|
|
70
|
+
* the account's `id`
|
|
71
|
+
*/
|
|
72
|
+
export declare function createChatBinding(accountKey: string, site: string, date: Date, options?: SiteOriginOptions): ChatBinding;
|
|
73
|
+
/**
|
|
74
|
+
* Check a binding's shape and recover who signed it. Throws on a message of
|
|
75
|
+
* any other shape and on a signature that is not 65 bytes with v 27 or 28 and
|
|
76
|
+
* a low s. A valid signature over a changed message recovers SOME address,
|
|
77
|
+
* which is why a reader compares `signer` with the account it expects
|
|
78
|
+
* (`acceptedChatBinding` does).
|
|
79
|
+
*/
|
|
80
|
+
export declare function verifyChatBinding(binding: ChatBinding, options?: SiteOriginOptions): VerifiedChatBinding;
|
|
81
|
+
/**
|
|
82
|
+
* The binding, if it verifies and names exactly this site, this chat address
|
|
83
|
+
* and this signer, otherwise undefined. Addresses compare in any case and
|
|
84
|
+
* with or without `0x`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function acceptedChatBinding(binding: ChatBinding | undefined, expected: ExpectedChatBinding, options?: SiteOriginOptions): ChatBinding | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* The connected apps with every chat binding removed except `site`'s own.
|
|
89
|
+
*
|
|
90
|
+
* Each site's binding publicly links the account to its chat on that site, and
|
|
91
|
+
* a proxy frame runs inside one site's page, where that page's scripts can
|
|
92
|
+
* reach what the frame holds. So everything handed to a frame keeps only the
|
|
93
|
+
* binding of the site it runs for. The account's own synced copy keeps all of
|
|
94
|
+
* them.
|
|
95
|
+
*/
|
|
96
|
+
export declare function onlySiteChatBinding<App extends {
|
|
97
|
+
appUrl: string;
|
|
98
|
+
chatBinding?: ChatBinding;
|
|
99
|
+
}>(apps: readonly App[], site: string): App[];
|
|
100
|
+
/**
|
|
101
|
+
* Of two bindings for the connection to `site` in `account`'s state, the one
|
|
102
|
+
* `account` signed for `site` over one it did not, then the later date, the
|
|
103
|
+
* first on a tie, and whichever is there when only one is. The date is the
|
|
104
|
+
* signer's own claim, and any message with a valid signature recovers SOME
|
|
105
|
+
* signer, so a binding whose date was edited, or that another key signed, must
|
|
106
|
+
* never outrank the account's own by claiming a later one. A binding is never
|
|
107
|
+
* withdrawn, only replaced by a newer one (docs/Chat-Binding.md), so this is
|
|
108
|
+
* the whole of how two copies of a connection merge theirs.
|
|
109
|
+
*
|
|
110
|
+
* @param account - the account's address, its `id`, in any case and with or
|
|
111
|
+
* without `0x`
|
|
112
|
+
*/
|
|
113
|
+
export declare function newerChatBinding(first: ChatBinding | undefined, second: ChatBinding | undefined, site: string, account: string): ChatBinding | undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `task` at most once per `cooldownMs` across all same-origin browser tabs.
|
|
3
|
+
*
|
|
4
|
+
* An exclusive Web Lock (`navigator.locks`, keyed on `lockName`) serialises
|
|
5
|
+
* concurrent callers so two tabs can't run `task` at the same instant; a shared
|
|
6
|
+
* `localStorage` timestamp (`cooldownKey`) then lets the second caller skip when
|
|
7
|
+
* another tab already ran within the window. `force` bypasses the cooldown (e.g.
|
|
8
|
+
* a page load / manual refresh) but still honours the optional short `graceMs`
|
|
9
|
+
* window, so a burst of forced runs (N tabs on load, or two back-to-back forced
|
|
10
|
+
* triggers) still collapses to one.
|
|
11
|
+
*
|
|
12
|
+
* Effect: with several tabs each scheduling the same task, only ONE actually runs
|
|
13
|
+
* per window — the rest observe the fresh timestamp and return. Whatever the task
|
|
14
|
+
* persists (e.g. the account document) then propagates to the other tabs via their
|
|
15
|
+
* own `storage` listeners, so they never need to fetch themselves.
|
|
16
|
+
*
|
|
17
|
+
* Falls back to running `task` directly when the Web Locks API is unavailable
|
|
18
|
+
* (Node / test), still honouring the cooldown when `localStorage` is present.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runCoalescedAcrossTabs(opts: {
|
|
21
|
+
lockName: string;
|
|
22
|
+
cooldownKey: string;
|
|
23
|
+
cooldownMs: number;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
/** Short window honoured even under `force` — collapses back-to-back forced runs. */
|
|
26
|
+
graceMs?: number;
|
|
27
|
+
task: () => Promise<void>;
|
|
28
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A file's path inside the folder it was picked with. `webkitdirectory` and a
|
|
3
|
+
* dropped directory prefix every path with the folder's own name; that segment
|
|
4
|
+
* goes, so `index.html` sits at the manifest root — the rule bee-js applies.
|
|
5
|
+
*/
|
|
6
|
+
export declare function collectionPath(file: Pick<File, "name" | "webkitRelativePath">): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ConnectionInfo } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Structural equality for {@link ConnectionInfo} snapshots, scoped to the
|
|
4
|
+
* fields the proxy emits to dApps. Used by the proxy to suppress redundant
|
|
5
|
+
* `connectionInfoChanged` events.
|
|
6
|
+
*
|
|
7
|
+
* `storagePartitioned` is compared via boolean coercion so a writer that
|
|
8
|
+
* sends `false` and one that omits the field (effectively `undefined`)
|
|
9
|
+
* are treated as equal — they mean the same thing to the dApp.
|
|
10
|
+
*/
|
|
11
|
+
export declare function connectionInfoEqual(a: ConnectionInfo | undefined, b: ConnectionInfo): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time and Session Constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized constants for time units and default session durations.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SECOND = 1000;
|
|
7
|
+
export declare const MINUTE: number;
|
|
8
|
+
export declare const HOUR: number;
|
|
9
|
+
export declare const DAY: number;
|
|
10
|
+
/**
|
|
11
|
+
* A saved session lets a site keep writing until its next load notices a
|
|
12
|
+
* revoke, so it is kept short. An account can set its own.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_SESSION_DURATION: number;
|
|
15
|
+
/**
|
|
16
|
+
* How long a connection to a dApp lasts, in ms: the account's own setting, or
|
|
17
|
+
* the default above.
|
|
18
|
+
*
|
|
19
|
+
* One rule with two callers on opposite sides of the handover — the popup
|
|
20
|
+
* writes `connectedUntil` from it when it saves the connection
|
|
21
|
+
* (`ui/src/lib/connect-handshake.ts`), and a partitioned proxy recomputes the
|
|
22
|
+
* same deadline when it hydrates (`hydratePartitionAccount`). Two copies of
|
|
23
|
+
* `?? DEFAULT_SESSION_DURATION` is one copy that goes stale when the rule
|
|
24
|
+
* gains a clamp or a floor (#590).
|
|
25
|
+
*/
|
|
26
|
+
export declare function appSessionDuration(settings?: {
|
|
27
|
+
appSessionDuration?: number;
|
|
28
|
+
}): number;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device ID Utilities
|
|
3
|
+
*
|
|
4
|
+
* Manages a per-browser device identifier stored in localStorage and
|
|
5
|
+
* provides helpers for merging device lists across sync/restore.
|
|
6
|
+
*/
|
|
7
|
+
import type { Device } from "../schemas";
|
|
8
|
+
/**
|
|
9
|
+
* Get the current device ID, creating one if it doesn't exist.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getOrCreateDeviceId(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Merge a device list with the current device.
|
|
14
|
+
*
|
|
15
|
+
* Upserts the current device: creates the entry if absent, otherwise leaves it
|
|
16
|
+
* as it is apart from the name. A `deviceName` we are given REPLACES the stored
|
|
17
|
+
* one, so a caller that learns a better label — the partition naming of #570 —
|
|
18
|
+
* corrects the row already in the registry instead of only the next one
|
|
19
|
+
* created. A caller with nothing to say passes nothing and the stored label
|
|
20
|
+
* stands: the name must not be blanked by a context that does not know it.
|
|
21
|
+
*
|
|
22
|
+
* A replacement carries `nameUpdatedAt`, the name's own merge clock, without
|
|
23
|
+
* which the correction never left this device: `mergeDevicesList` ranks records
|
|
24
|
+
* on sign-in recency, a rename moves none of it, and a tie favours the local
|
|
25
|
+
* copy — so every other context kept its stale label forever (#663). Only an
|
|
26
|
+
* ACTUAL change stamps it. Both callers are polls, and a clock that moved every
|
|
27
|
+
* time a caller passed the name it already stored would make every quiet round
|
|
28
|
+
* a byte change to persist, which is the churn #652 removed.
|
|
29
|
+
*
|
|
30
|
+
* Touches neither `removedAt` nor `lastSignedInAt` on an existing entry. Both
|
|
31
|
+
* callers are refresh paths — the proxy's throttled roster poll and the UI's
|
|
32
|
+
* periodic fold — and a poll is not a sign-in: clearing our own tombstone here
|
|
33
|
+
* undid every removal on the next round (#611), and stamping `lastSignedInAt`
|
|
34
|
+
* here (#652) made every fold a byte change to persist, which a proxy holding
|
|
35
|
+
* a partition answered with a bus delta and a stamped feed write. #337's "a
|
|
36
|
+
* genuine sign-in re-activates a device removed elsewhere" still holds; it
|
|
37
|
+
* lives at the sign-in seam instead (`reactivateThisDevice`, the only one there
|
|
38
|
+
* is), which is the only place that can tell a user unlocking their account
|
|
39
|
+
* from a timer firing.
|
|
40
|
+
* Liveness is not this field's job either — the bus carries it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function mergeDevices(existing: Device[], currentDeviceId: string, deviceName?: string): Device[];
|
|
43
|
+
/**
|
|
44
|
+
* Detect a human-readable name for the current device and browser.
|
|
45
|
+
* Returns a string like "Chrome on Linux Desktop" or "Safari on iPhone".
|
|
46
|
+
*/
|
|
47
|
+
export declare function detectDeviceName(): string;
|
|
48
|
+
/**
|
|
49
|
+
* The device name a STORAGE-PARTITIONED proxy iframe announces itself under.
|
|
50
|
+
*
|
|
51
|
+
* A partitioned iframe keeps its own `swarm-id-device-id` in the partition, and
|
|
52
|
+
* a partition is one (top-level site, iframe origin) pair — so two tabs of one
|
|
53
|
+
* dApp are one device, while two different dApps on the same browser are two.
|
|
54
|
+
* `detectDeviceName()` alone therefore hands a Safari user with three connected
|
|
55
|
+
* dApps three indistinguishable "Safari on Mac" rows (#570). The dApp host is
|
|
56
|
+
* what tells them apart, and the port comes with it: locally the dApps differ
|
|
57
|
+
* by port alone.
|
|
58
|
+
*
|
|
59
|
+
* Only for the partitioned case. An UNPARTITIONED proxy reads the trusted
|
|
60
|
+
* domain's first-party store, so it shares `swarm-id-device-id` with the
|
|
61
|
+
* identity UI — it is the same device, and naming that after whichever dApp
|
|
62
|
+
* happened to load it would be wrong.
|
|
63
|
+
*
|
|
64
|
+
* The host is finer-grained than the partition key, which is schemeful site
|
|
65
|
+
* (eTLD+1) and may drop the port. Two same-site dApps — `app1.foo.com` and
|
|
66
|
+
* `app2.foo.com`, or two localhost ports — therefore share ONE partition and
|
|
67
|
+
* one device row, and each announces that row under its own host, so the
|
|
68
|
+
* stored name alternates between them on their polls.
|
|
69
|
+
*
|
|
70
|
+
* The label is per-origin and the thing it labels is per-site, and both are
|
|
71
|
+
* right for their own job. The name is per-origin because the origin is what a
|
|
72
|
+
* user recognises. The row is per-site because it stands for a WRITER, not for
|
|
73
|
+
* a security boundary: nothing reads the device registry to decide whether a
|
|
74
|
+
* write is allowed (`knownDeviceIds` only feeds the lease rival set), and two
|
|
75
|
+
* contexts in one partition cannot write at the same time anyway — they share
|
|
76
|
+
* `withBatchWriteLock`, a Web Lock scoped to the store they share. Giving them
|
|
77
|
+
* a row each would spend two of a finite `partitionCount` on writers that are
|
|
78
|
+
* already serialised. Where per-origin state really is per-origin, the storage
|
|
79
|
+
* key says so: the stored session (#671), and the stamper's.
|
|
80
|
+
*
|
|
81
|
+
* What the alternation costs: nothing on the refresh itself. A partitioned
|
|
82
|
+
* proxy keeps the merged registry in memory, and the refresh that produces it
|
|
83
|
+
* is a pre-acquire hook rather than a publish, so a flip writes nothing and
|
|
84
|
+
* sends nothing. It does not stay in memory, though — the next bus-delta fold
|
|
85
|
+
* persists it, since `refreshStoredPartitionAccount` serialises the whole
|
|
86
|
+
* in-memory account, merged devices included, into the session record. And a
|
|
87
|
+
* flipped row that travels for any other reason (a delta, a feed publish)
|
|
88
|
+
* carries a newer name clock, so it wins the name wherever it lands rather
|
|
89
|
+
* than tying and losing: the alternation reaches the account, not just the two
|
|
90
|
+
* iframes.
|
|
91
|
+
*/
|
|
92
|
+
export declare function partitionDeviceName(appOrigin: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Whether a merged device registry is worth persisting over the stored one.
|
|
95
|
+
*
|
|
96
|
+
* The old test was "did the list get LONGER", which threw away every merge that
|
|
97
|
+
* only refreshed a peer's `lastSignedInAt` or set a `removedAt` tombstone — and
|
|
98
|
+
* those are exactly the fields `activeDeviceIds` prunes the rival set on. A peer
|
|
99
|
+
* the roster shows as live could therefore age out of the stored copy, drop out
|
|
100
|
+
* of the rival set, and never get an intent read: the dual-acquire the registry
|
|
101
|
+
* refresh exists to prevent (#586).
|
|
102
|
+
*
|
|
103
|
+
* Every field counts, our own included. `mergeDevices` no longer stamps our
|
|
104
|
+
* `lastSignedInAt` on a poll (#652), so when it moves it is a genuine sign-in
|
|
105
|
+
* (a reactivation) and worth a save. Until then this excluded it, because
|
|
106
|
+
* counting a stamp that moved on every call meant a save and a storage event in
|
|
107
|
+
* every other tab, every poll round.
|
|
108
|
+
*/
|
|
109
|
+
export declare function deviceRegistryChanged(stored: Device[], merged: Device[], selfDeviceId: string): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hex utilities
|
|
3
|
+
*
|
|
4
|
+
* Canonical location for hex conversion functions
|
|
5
|
+
*/
|
|
6
|
+
import type { Address } from "../schemas";
|
|
7
|
+
/**
|
|
8
|
+
* Convert a hex string (optional 0x prefix) to Uint8Array
|
|
9
|
+
*
|
|
10
|
+
* @param hexString - Hex string (e.g., "deadbeef" or "0xdeadbeef")
|
|
11
|
+
* @returns Uint8Array
|
|
12
|
+
* @throws {Error} If the input has odd length or non-hex characters — a
|
|
13
|
+
* malformed value must fail loudly, not silently decode to zero bytes
|
|
14
|
+
*/
|
|
15
|
+
export declare function hexToUint8Array(hexString: string): Uint8Array;
|
|
16
|
+
/**
|
|
17
|
+
* Convert a Uint8Array to hex string
|
|
18
|
+
*
|
|
19
|
+
* @param bytes - Uint8Array to convert
|
|
20
|
+
* @returns Hex string (e.g., "deadbeef")
|
|
21
|
+
*/
|
|
22
|
+
export declare function uint8ArrayToHex(bytes: Uint8Array): string;
|
|
23
|
+
/**
|
|
24
|
+
* Create a validated hex address string (40 lowercase hex chars).
|
|
25
|
+
* Accepts optional 0x prefix and any case.
|
|
26
|
+
*
|
|
27
|
+
* @param input - Ethereum address string (with or without 0x prefix)
|
|
28
|
+
* @returns Normalized 40-character lowercase hex string
|
|
29
|
+
* @throws {Error} If the input is not a valid 40-char hex address
|
|
30
|
+
*/
|
|
31
|
+
export declare function hexAddress(input: string): Address;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** A workable deadline for a chain read over a public RPC. */
|
|
2
|
+
export declare const CHAIN_READ_TIMEOUT_MS = 10000;
|
|
3
|
+
export interface JsonRpcOptions {
|
|
4
|
+
/** Deadline for the whole request, including the response body. */
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
/**
|
|
7
|
+
* How the endpoint is named in error messages, e.g. "The configured Gnosis
|
|
8
|
+
* RPC". Defaults to the endpoint's origin.
|
|
9
|
+
*/
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
/** One call in a batch. Ids are assigned by {@link jsonRpcBatch}. */
|
|
13
|
+
export interface JsonRpcRequest {
|
|
14
|
+
method: string;
|
|
15
|
+
params: unknown[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* One checked JSON-RPC call. `T` is a cast, not a validation — a caller that
|
|
19
|
+
* reads a field off the result should check that field's type itself.
|
|
20
|
+
*
|
|
21
|
+
* @throws if the transport fails, the endpoint answers non-2xx, or the response
|
|
22
|
+
* carries an `error` or no usable `result`; a {@link TimeoutError} once the
|
|
23
|
+
* deadline passes.
|
|
24
|
+
*/
|
|
25
|
+
export declare function jsonRpcCall<T>(rpcUrl: string, method: string, params: unknown[], options: JsonRpcOptions): Promise<T>;
|
|
26
|
+
/**
|
|
27
|
+
* A batch of checked calls, returned in the same order as `requests`. Responses
|
|
28
|
+
* are matched back by id, since JSON-RPC does not promise batch ordering.
|
|
29
|
+
*
|
|
30
|
+
* @throws if the transport fails, the batch comes back the wrong shape or with
|
|
31
|
+
* ids that do not match the requests, or ANY member fails the contract —
|
|
32
|
+
* a partial batch is not an answer.
|
|
33
|
+
*/
|
|
34
|
+
export declare function jsonRpcBatch(rpcUrl: string, requests: JsonRpcRequest[], options: JsonRpcOptions): Promise<unknown[]>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { hexToUint8Array, uint8ArrayToHex } from "./hex";
|
|
2
|
+
/** Master key → the stored derivation key */
|
|
3
|
+
export declare const DERIVATION_KEY_LABEL = "derivation-key";
|
|
4
|
+
/** Derivation key → the Swarm encryption key */
|
|
5
|
+
export declare const SWARM_ENCRYPTION_LABEL = "swarm-encryption";
|
|
6
|
+
/** Derivation key → the postage signer that stamps the account's uploads */
|
|
7
|
+
export declare const POSTAGE_SIGNER_LABEL = "postage-signer";
|
|
8
|
+
/** Swarm encryption key → the backup signer that owns every feed */
|
|
9
|
+
export declare const BACKUP_KEY_LABEL = "backup-key";
|
|
10
|
+
/** Derivation key → the account-wide ACT sharing key (#519) */
|
|
11
|
+
export declare const ACT_SHARING_LABEL = "act-sharing";
|
|
12
|
+
/**
|
|
13
|
+
* App secret → the site's chat key, the key a site's chat signs with and whose
|
|
14
|
+
* address the site's chat binding names (docs/Chat-Binding.md).
|
|
15
|
+
*
|
|
16
|
+
* The version in the label is how a leaked chat key is replaced: a new label
|
|
17
|
+
* gives every account a new chat key on every site, the identity UI then signs
|
|
18
|
+
* a fresh binding at the next connect because the kept one names the old
|
|
19
|
+
* address, and readers stop accepting the old key because no binding names it.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CHAT_KEY_LABEL = "swarm-id chat key v1";
|
|
22
|
+
/**
|
|
23
|
+
* Derive a secret from a parent key under a label, with Web Crypto.
|
|
24
|
+
*
|
|
25
|
+
* HMAC-SHA256: deterministic, so the same parent key and label always give
|
|
26
|
+
* the same secret. `deriveSecretSync` is the same function without the
|
|
27
|
+
* `await`, for a caller that cannot have one; the test pins the two equal.
|
|
28
|
+
*
|
|
29
|
+
* @param parentKey - The key to derive from (hex string)
|
|
30
|
+
* @param label - What the derived key is for: one of the labels above, or the
|
|
31
|
+
* app's origin (e.g. "https://swarm-app.local:8080") for a per-app secret
|
|
32
|
+
* @returns The derived secret as a hex string
|
|
33
|
+
*/
|
|
34
|
+
export declare function deriveSecret(parentKey: string, label: string): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* `deriveSecret` without the `await`: the same HMAC-SHA256 computed with
|
|
37
|
+
* `@noble/hashes`, for a caller that cannot await — `buildConnectionInfo` in
|
|
38
|
+
* the proxy builds ConnectionInfo synchronously (#692). Byte-identical to the
|
|
39
|
+
* Web Crypto path, which the test pins.
|
|
40
|
+
*/
|
|
41
|
+
export declare function deriveSecretSync(parentKey: string, label: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Derive an AES-GCM-256 key from `secretHex` under `label`.
|
|
44
|
+
*
|
|
45
|
+
* The three steps — HMAC to a fresh secret, hex to bytes, import as AES-GCM —
|
|
46
|
+
* are one pipeline. Non-extractable and encrypt/decrypt only, which is the
|
|
47
|
+
* part worth stating once (#590). The bus envelope (`deriveBusContext`) is the
|
|
48
|
+
* live caller; the `.swarmid` file key is the identity UI's, derived by HKDF
|
|
49
|
+
* from the recovery-phrase entropy, and never comes through here.
|
|
50
|
+
*/
|
|
51
|
+
export declare function deriveAesGcmKey(secretHex: string, label: string): Promise<CryptoKey>;
|
|
52
|
+
/**
|
|
53
|
+
* Derive account derivation key from account master key
|
|
54
|
+
*
|
|
55
|
+
* A generic 32-byte root key stored on the account, used for deterministically
|
|
56
|
+
* deriving further keys (e.g. swarmEncryptionKey) without re-authentication.
|
|
57
|
+
*
|
|
58
|
+
* @param accountMasterKey - Account master key (hex string)
|
|
59
|
+
* @returns 32-byte derivation key (as hex string)
|
|
60
|
+
*/
|
|
61
|
+
export declare function deriveAccountDerivationKey(accountMasterKey: string): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Derive Swarm encryption key from account derivation key
|
|
64
|
+
*
|
|
65
|
+
* Used for encrypting account snapshot data before upload to Swarm.
|
|
66
|
+
* Derived from the stored derivationKey rather than the master key directly.
|
|
67
|
+
*
|
|
68
|
+
* @param derivationKey - Account derivation key (hex string)
|
|
69
|
+
* @returns 32-byte encryption key (as hex string)
|
|
70
|
+
*/
|
|
71
|
+
export declare function deriveSwarmEncryptionKey(derivationKey: string): Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Derive the account-level postage batch signer key from the account derivation
|
|
74
|
+
* key. Used as the PrivateKey for signing chunks uploaded with a postage batch.
|
|
75
|
+
*
|
|
76
|
+
* @param derivationKey - Account derivation key (hex string)
|
|
77
|
+
* @returns 32-byte signer key (as hex string)
|
|
78
|
+
*/
|
|
79
|
+
export declare function derivePostageSignerKey(derivationKey: string): Promise<string>;
|
|
80
|
+
/**
|
|
81
|
+
* `derivePostageSignerKey` without the `await`, for the proxy's ConnectionInfo
|
|
82
|
+
* (#815): the signer's address is what an app buys a batch for.
|
|
83
|
+
*/
|
|
84
|
+
export declare function derivePostageSignerKeySync(derivationKey: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* The account-wide ACT key (#519): what other people grant access to when they
|
|
87
|
+
* mean the person rather than one of their apps. Derived from the derivation
|
|
88
|
+
* key, so every app origin and every device of the account arrives at the same
|
|
89
|
+
* key — and the master key still never leaves the identity UI. Its public half
|
|
90
|
+
* is `identity.sharingPublicKey`. Synchronous because ConnectionInfo is built
|
|
91
|
+
* without awaiting.
|
|
92
|
+
*/
|
|
93
|
+
export declare function deriveSharingKey(derivationKey: string): {
|
|
94
|
+
secret: Uint8Array;
|
|
95
|
+
publicKey: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* The first output of `derive` that is a valid secp256k1 private key, a
|
|
99
|
+
* number from 1 to n - 1 where n is the curve order. It asks under `label`
|
|
100
|
+
* first, then under `label/1`, `label/2` and so on. An HMAC-SHA256 output
|
|
101
|
+
* misses that range with a chance of about 2^-128, so in practice the first
|
|
102
|
+
* answer is the key, but the rule is fixed so every device agrees even then.
|
|
103
|
+
*
|
|
104
|
+
* @throws if no candidate within the attempt bound is a key, which only a
|
|
105
|
+
* broken `derive` can cause
|
|
106
|
+
*/
|
|
107
|
+
export declare function firstValidPrivateKey(derive: (label: string) => Uint8Array, label: string): Uint8Array;
|
|
108
|
+
/**
|
|
109
|
+
* The site's chat key, from the site's app secret
|
|
110
|
+
* (`deriveSecret(masterKey, origin)`): `HMAC(appSecret, CHAT_KEY_LABEL)`, the
|
|
111
|
+
* same derivation `deriveAppSecret` runs for an app's own label, held to a
|
|
112
|
+
* valid secp256k1 key by `firstValidPrivateKey`. It depends on the account and
|
|
113
|
+
* the site alone, so every device of the account derives the same key.
|
|
114
|
+
* Synchronous because ConnectionInfo and the binding are built without
|
|
115
|
+
* awaiting.
|
|
116
|
+
*
|
|
117
|
+
* @param appSecret - the site's app secret (bare hex)
|
|
118
|
+
* @returns the 32-byte chat private key
|
|
119
|
+
*/
|
|
120
|
+
export declare function deriveChatKey(appSecret: string): Uint8Array;
|
|
121
|
+
export declare const utils: {
|
|
122
|
+
hexToUint8Array: typeof hexToUint8Array;
|
|
123
|
+
uint8ArrayToHex: typeof uint8ArrayToHex;
|
|
124
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Address derivation for the per-partition lock SOC. Lives in `utils/` (not
|
|
3
|
+
* `sync/`) so it can be imported by both `sync/partition-lock.ts` (the lock
|
|
4
|
+
* SOC reader/writer) and `utils/batch-utilization.ts` (the stamper that
|
|
5
|
+
* routes lock-SOC overstamps to the reserved slot) without a circular
|
|
6
|
+
* dependency through `toBucket`.
|
|
7
|
+
*/
|
|
8
|
+
import { Identifier, type BatchId, type EthAddress } from "@ethersphere/bee-js";
|
|
9
|
+
/**
|
|
10
|
+
* Build the lock-SOC identifier for one partition of one batch. Universal
|
|
11
|
+
* across accounts — the per-account `owner` carries that separation — so the
|
|
12
|
+
* identifier encodes what remains: which batch, and which of its lanes.
|
|
13
|
+
*
|
|
14
|
+
* The batch is in here because it is in everything the lock protects. A lane's
|
|
15
|
+
* slot space is computed WITHIN a batch (`dataSlot`), and the state riding it
|
|
16
|
+
* is published under `makePartitionStateTopic(batchId, partition)`. Keying the
|
|
17
|
+
* lock on the partition alone made lane p of batch X and lane p of batch Y one
|
|
18
|
+
* chunk address, so two coordinators that share no slot took turns anyway —
|
|
19
|
+
* and per-app stamp overrides put two live coordinators on two batches without
|
|
20
|
+
* any multi-batch feature (#589).
|
|
21
|
+
*/
|
|
22
|
+
export declare function makePartitionLockIdentifier(batchId: BatchId, partition: number): Identifier;
|
|
23
|
+
/**
|
|
24
|
+
* 32-byte SOC chunk address for the (batch, partition, owner) lock SOC. Used
|
|
25
|
+
* by the stamper to detect "this stamp is the lock SOC's overstamp" and route
|
|
26
|
+
* it to the reserved slot.
|
|
27
|
+
*/
|
|
28
|
+
export declare function lockSocAddress(batchId: BatchId, partition: number, owner: EthAddress): Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* Bucket index that the partition-lock SOC for (batch, partition, owner) lands
|
|
31
|
+
* in. Lock-SOC writes overstamp this single fixed bucket; the partition stamper
|
|
32
|
+
* reserves the bucket from data uploads so the tight heartbeat cadence
|
|
33
|
+
* doesn't exhaust the bucket's slot budget.
|
|
34
|
+
*/
|
|
35
|
+
export declare function lockSocBucket(batchId: BatchId, partition: number, owner: EthAddress): number;
|