@snaha/swarm-id 0.0.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/README.md +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- package/src/utils/versioned-storage.ts +323 -0
|
@@ -0,0 +1,3095 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ClientOptions,
|
|
3
|
+
ConnectOptions,
|
|
4
|
+
AuthStatus,
|
|
5
|
+
ConnectionInfo,
|
|
6
|
+
UploadResult,
|
|
7
|
+
FileData,
|
|
8
|
+
UploadOptions,
|
|
9
|
+
ActUploadOptions,
|
|
10
|
+
DownloadOptions,
|
|
11
|
+
RequestOptions,
|
|
12
|
+
Reference,
|
|
13
|
+
SOCReader,
|
|
14
|
+
SOCWriter,
|
|
15
|
+
SingleOwnerChunk,
|
|
16
|
+
SocRawUploadResult,
|
|
17
|
+
SocRawUploadResponseMessage,
|
|
18
|
+
SocRawUploadMessage,
|
|
19
|
+
SocGetOwnerMessage,
|
|
20
|
+
SocGetOwnerResponseMessage,
|
|
21
|
+
FeedReaderOptions,
|
|
22
|
+
FeedWriterOptions,
|
|
23
|
+
FeedReader,
|
|
24
|
+
FeedWriter,
|
|
25
|
+
EpochFeedDownloadReferenceMessage,
|
|
26
|
+
EpochFeedDownloadReferenceResponseMessage,
|
|
27
|
+
EpochFeedUploadReferenceMessage,
|
|
28
|
+
EpochFeedUploadReferenceResponseMessage,
|
|
29
|
+
FeedGetOwnerMessage,
|
|
30
|
+
FeedGetOwnerResponseMessage,
|
|
31
|
+
EpochFeedDownloadOptions,
|
|
32
|
+
EpochFeedDownloadPayloadResult,
|
|
33
|
+
EpochFeedDownloadReferenceResult,
|
|
34
|
+
EpochFeedUploadOptions,
|
|
35
|
+
EpochFeedUploadResult,
|
|
36
|
+
SequentialFeedReaderOptions,
|
|
37
|
+
SequentialFeedWriterOptions,
|
|
38
|
+
SequentialFeedUpdateOptions,
|
|
39
|
+
SequentialFeedUploadOptions,
|
|
40
|
+
SequentialFeedDownloadRawOptions,
|
|
41
|
+
SequentialFeedUploadRawOptions,
|
|
42
|
+
SequentialFeedPayloadResult,
|
|
43
|
+
SequentialFeedReferenceResult,
|
|
44
|
+
SequentialFeedUploadResult,
|
|
45
|
+
SequentialFeedReader,
|
|
46
|
+
SequentialFeedWriter,
|
|
47
|
+
SequentialFeedGetOwnerMessage,
|
|
48
|
+
SequentialFeedGetOwnerResponseMessage,
|
|
49
|
+
SequentialFeedDownloadPayloadMessage,
|
|
50
|
+
SequentialFeedDownloadPayloadResponseMessage,
|
|
51
|
+
SequentialFeedDownloadRawPayloadMessage,
|
|
52
|
+
SequentialFeedDownloadRawPayloadResponseMessage,
|
|
53
|
+
SequentialFeedDownloadReferenceMessage,
|
|
54
|
+
SequentialFeedDownloadReferenceResponseMessage,
|
|
55
|
+
SequentialFeedUploadPayloadMessage,
|
|
56
|
+
SequentialFeedUploadPayloadResponseMessage,
|
|
57
|
+
SequentialFeedUploadRawPayloadMessage,
|
|
58
|
+
SequentialFeedUploadRawPayloadResponseMessage,
|
|
59
|
+
SequentialFeedUploadReferenceMessage,
|
|
60
|
+
SequentialFeedUploadReferenceResponseMessage,
|
|
61
|
+
CreateFeedManifestMessage,
|
|
62
|
+
CreateFeedManifestResponseMessage,
|
|
63
|
+
SocDownloadMessage,
|
|
64
|
+
SocDownloadResponseMessage,
|
|
65
|
+
SocRawDownloadMessage,
|
|
66
|
+
SocRawDownloadResponseMessage,
|
|
67
|
+
SocUploadResult,
|
|
68
|
+
SocUploadResponseMessage,
|
|
69
|
+
SocUploadMessage,
|
|
70
|
+
ParentToIframeMessage,
|
|
71
|
+
IframeToParentMessage,
|
|
72
|
+
AppMetadata,
|
|
73
|
+
ButtonConfig,
|
|
74
|
+
PostageBatch,
|
|
75
|
+
} from "./types"
|
|
76
|
+
import {
|
|
77
|
+
IframeToParentMessageSchema,
|
|
78
|
+
ParentToIframeMessageSchema,
|
|
79
|
+
AppMetadataSchema,
|
|
80
|
+
} from "./types"
|
|
81
|
+
import { EthAddress, Identifier, PrivateKey, Topic } from "@ethersphere/bee-js"
|
|
82
|
+
import { uint8ArrayToHex } from "./utils/key-derivation"
|
|
83
|
+
|
|
84
|
+
const DEFAULT_TIMEOUT_MS = 30000
|
|
85
|
+
const DEFAULT_INITIALIZATION_TIMEOUT_MS = 30000
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Main client library for integrating Swarm ID authentication and storage capabilities
|
|
89
|
+
* into web applications.
|
|
90
|
+
*
|
|
91
|
+
* SwarmIdClient enables parent windows to interact with a Swarm ID iframe proxy,
|
|
92
|
+
* providing secure authentication, identity management, and data upload/download
|
|
93
|
+
* functionality to the Swarm decentralized storage network.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const client = new SwarmIdClient({
|
|
98
|
+
* iframeOrigin: 'https://swarm-id.example.com',
|
|
99
|
+
* metadata: {
|
|
100
|
+
* name: 'My App',
|
|
101
|
+
* description: 'A decentralized application'
|
|
102
|
+
* },
|
|
103
|
+
* onAuthChange: (authenticated) => {
|
|
104
|
+
* console.log('Auth status changed:', authenticated)
|
|
105
|
+
* }
|
|
106
|
+
* })
|
|
107
|
+
*
|
|
108
|
+
* await client.initialize()
|
|
109
|
+
*
|
|
110
|
+
* const status = await client.checkAuthStatus()
|
|
111
|
+
* if (status.authenticated) {
|
|
112
|
+
* const result = await client.uploadData(new Uint8Array([1, 2, 3]))
|
|
113
|
+
* console.log('Uploaded with reference:', result.reference)
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export class SwarmIdClient {
|
|
118
|
+
private iframe: HTMLIFrameElement | undefined
|
|
119
|
+
private iframeOrigin: string
|
|
120
|
+
private iframePath: string
|
|
121
|
+
private timeout: number
|
|
122
|
+
private initializationTimeout: number
|
|
123
|
+
private onAuthChange?: (authenticated: boolean) => void
|
|
124
|
+
private popupMode: "popup" | "window"
|
|
125
|
+
private metadata: AppMetadata
|
|
126
|
+
private buttonConfig?: ButtonConfig
|
|
127
|
+
private containerId?: string
|
|
128
|
+
private ready: boolean = false
|
|
129
|
+
private readyPromise: Promise<void>
|
|
130
|
+
private readyResolve?: () => void
|
|
131
|
+
private readyReject?: (error: Error) => void
|
|
132
|
+
private pendingRequests: Map<
|
|
133
|
+
string,
|
|
134
|
+
{
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
resolve: (value: any) => void
|
|
137
|
+
reject: (error: Error) => void
|
|
138
|
+
timeoutId: NodeJS.Timeout
|
|
139
|
+
}
|
|
140
|
+
> = new Map()
|
|
141
|
+
private requestIdCounter = 0
|
|
142
|
+
private messageListener: ((event: MessageEvent) => void) | undefined
|
|
143
|
+
private proxyInitializedPromise: Promise<void>
|
|
144
|
+
private proxyInitializedResolve?: () => void
|
|
145
|
+
private proxyInitializedReject?: (error: Error) => void
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates a new SwarmIdClient instance.
|
|
149
|
+
*
|
|
150
|
+
* @param options - Configuration options for the client
|
|
151
|
+
* @param options.iframeOrigin - The origin URL where the Swarm ID proxy iframe is hosted
|
|
152
|
+
* @param options.iframePath - The path to the proxy iframe (defaults to "/proxy")
|
|
153
|
+
* @param options.timeout - Request timeout in milliseconds (defaults to 30000)
|
|
154
|
+
* @param options.onAuthChange - Callback function invoked when authentication status changes
|
|
155
|
+
* @param options.popupMode - How to display the authentication popup: "popup" or "window" (defaults to "window")
|
|
156
|
+
* @param options.metadata - Application metadata shown to users during authentication
|
|
157
|
+
* @param options.metadata.name - Application name (1-100 characters)
|
|
158
|
+
* @param options.metadata.description - Optional application description (max 500 characters)
|
|
159
|
+
* @param options.metadata.icon - Optional application icon as a data URL (SVG or PNG, max 4KB)
|
|
160
|
+
* @param options.buttonConfig - Button configuration for the authentication UI (optional)
|
|
161
|
+
* @param options.buttonConfig.connectText - Text for the connect button (optional)
|
|
162
|
+
* @param options.buttonConfig.disconnectText - Text for the disconnect button (optional)
|
|
163
|
+
* @param options.buttonConfig.loadingText - Text shown during loading (optional)
|
|
164
|
+
* @param options.buttonConfig.backgroundColor - Background color for buttons (optional)
|
|
165
|
+
* @param options.buttonConfig.color - Text color for buttons (optional)
|
|
166
|
+
* @param options.buttonConfig.borderRadius - Border radius for buttons and iframe (optional)
|
|
167
|
+
* @param options.containerId - ID of container element to place iframe in (optional)
|
|
168
|
+
* @throws {Error} If the provided app metadata is invalid
|
|
169
|
+
*/
|
|
170
|
+
constructor(options: ClientOptions) {
|
|
171
|
+
this.iframeOrigin = options.iframeOrigin
|
|
172
|
+
this.iframePath = options.iframePath || "/proxy"
|
|
173
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS
|
|
174
|
+
this.initializationTimeout =
|
|
175
|
+
options.initializationTimeout ?? DEFAULT_INITIALIZATION_TIMEOUT_MS
|
|
176
|
+
this.onAuthChange = options.onAuthChange
|
|
177
|
+
this.popupMode = options.popupMode || "window"
|
|
178
|
+
this.metadata = options.metadata
|
|
179
|
+
this.buttonConfig = options.buttonConfig
|
|
180
|
+
this.containerId = options.containerId
|
|
181
|
+
|
|
182
|
+
// Validate metadata
|
|
183
|
+
try {
|
|
184
|
+
AppMetadataSchema.parse(this.metadata)
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw new Error(`Invalid app metadata: ${error}`)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Create promise that resolves when iframe is ready
|
|
190
|
+
this.readyPromise = new Promise<void>((resolve, reject) => {
|
|
191
|
+
this.readyResolve = resolve
|
|
192
|
+
this.readyReject = reject
|
|
193
|
+
|
|
194
|
+
// Timeout if proxy doesn't respond
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
reject(
|
|
197
|
+
new Error(
|
|
198
|
+
`Proxy initialization timeout - proxy did not respond within ${this.initializationTimeout}ms`,
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
}, this.initializationTimeout)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// Create promise for proxyInitialized message
|
|
205
|
+
this.proxyInitializedPromise = new Promise<void>((resolve, reject) => {
|
|
206
|
+
this.proxyInitializedResolve = resolve
|
|
207
|
+
this.proxyInitializedReject = reject
|
|
208
|
+
|
|
209
|
+
// Timeout if proxy doesn't send proxyInitialized
|
|
210
|
+
setTimeout(() => {
|
|
211
|
+
if (this.proxyInitializedReject) {
|
|
212
|
+
this.proxyInitializedReject(
|
|
213
|
+
new Error(
|
|
214
|
+
`Proxy initialization timeout - proxy did not signal readiness within ${this.initializationTimeout}ms`,
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
}, this.initializationTimeout)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
this.setupMessageListener()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Initializes the client by creating and embedding the proxy iframe.
|
|
226
|
+
*
|
|
227
|
+
* This method must be called before using any other client methods.
|
|
228
|
+
* It creates a hidden iframe, waits for the proxy to initialize,
|
|
229
|
+
* identifies the parent application to the proxy, and waits for
|
|
230
|
+
* the proxy to signal readiness.
|
|
231
|
+
*
|
|
232
|
+
* @returns A promise that resolves when the client is fully initialized
|
|
233
|
+
* @throws {Error} If the client is already initialized
|
|
234
|
+
* @throws {Error} If the iframe fails to load
|
|
235
|
+
* @throws {Error} If the proxy does not respond within the timeout period (30 seconds)
|
|
236
|
+
* @throws {Error} If origin validation fails on the proxy side
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```typescript
|
|
240
|
+
* const client = new SwarmIdClient({ ... })
|
|
241
|
+
* try {
|
|
242
|
+
* await client.initialize()
|
|
243
|
+
* console.log('Client ready')
|
|
244
|
+
* } catch (error) {
|
|
245
|
+
* console.error('Failed to initialize:', error)
|
|
246
|
+
* }
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
async initialize(): Promise<void> {
|
|
250
|
+
if (this.iframe) {
|
|
251
|
+
throw new Error("SwarmIdClient already initialized")
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Create iframe for proxy
|
|
255
|
+
this.iframe = document.createElement("iframe")
|
|
256
|
+
this.iframe.src = `${this.iframeOrigin}${this.iframePath}`
|
|
257
|
+
|
|
258
|
+
// Common iframe styles
|
|
259
|
+
this.iframe.style.border = "none"
|
|
260
|
+
this.iframe.style.backgroundColor = "transparent"
|
|
261
|
+
this.iframe.style.borderRadius = this.buttonConfig?.borderRadius || "0"
|
|
262
|
+
|
|
263
|
+
// Determine where to place the iframe
|
|
264
|
+
let containerElement: HTMLElement | undefined
|
|
265
|
+
if (this.containerId) {
|
|
266
|
+
containerElement = document.getElementById(this.containerId) || undefined
|
|
267
|
+
if (!containerElement) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Container element with ID "${this.containerId}" not found`,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Fill the container
|
|
274
|
+
this.iframe.style.width = "100%"
|
|
275
|
+
this.iframe.style.height = "100%"
|
|
276
|
+
this.iframe.style.display = "block"
|
|
277
|
+
} else {
|
|
278
|
+
// Default: fixed position in bottom-right corner (hidden by default)
|
|
279
|
+
this.iframe.style.display = "none"
|
|
280
|
+
this.iframe.style.position = "fixed"
|
|
281
|
+
this.iframe.style.bottom = "20px"
|
|
282
|
+
this.iframe.style.right = "20px"
|
|
283
|
+
this.iframe.style.width = "300px"
|
|
284
|
+
this.iframe.style.height = "50px"
|
|
285
|
+
this.iframe.style.zIndex = "999999"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Wait for iframe to load
|
|
289
|
+
await new Promise<void>((resolve, reject) => {
|
|
290
|
+
this.iframe!.onload = () => resolve()
|
|
291
|
+
this.iframe!.onerror = () =>
|
|
292
|
+
reject(new Error("Failed to load Swarm ID iframe"))
|
|
293
|
+
|
|
294
|
+
// Append to container or body
|
|
295
|
+
if (containerElement) {
|
|
296
|
+
containerElement.appendChild(this.iframe!)
|
|
297
|
+
} else {
|
|
298
|
+
document.body.appendChild(this.iframe!)
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// Wait for proxy to signal it's ready
|
|
303
|
+
await this.proxyInitializedPromise
|
|
304
|
+
|
|
305
|
+
// Identify ourselves to the iframe
|
|
306
|
+
this.sendMessage({
|
|
307
|
+
type: "parentIdentify",
|
|
308
|
+
popupMode: this.popupMode,
|
|
309
|
+
metadata: this.metadata,
|
|
310
|
+
buttonConfig: this.buttonConfig,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// Wait for iframe to be ready
|
|
314
|
+
await this.readyPromise
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Setup message listener for iframe responses
|
|
319
|
+
*/
|
|
320
|
+
private setupMessageListener(): void {
|
|
321
|
+
this.messageListener = (event: MessageEvent) => {
|
|
322
|
+
// Handle proxyInitialized BEFORE any validation to avoid race condition
|
|
323
|
+
// This message is sent immediately when iframe loads and uses wildcard origin
|
|
324
|
+
if (event.data?.type === "proxyInitialized") {
|
|
325
|
+
// Security: Verify message is from OUR iframe (not another window/iframe)
|
|
326
|
+
if (this.iframe && event.source === this.iframe.contentWindow) {
|
|
327
|
+
if (this.proxyInitializedResolve) {
|
|
328
|
+
this.proxyInitializedResolve()
|
|
329
|
+
this.proxyInitializedResolve = undefined // Prevent double resolution
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
console.warn(
|
|
333
|
+
"[SwarmIdClient] Rejected proxyInitialized from unknown source",
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Validate origin (extract just origin part, ignoring any path in iframeOrigin)
|
|
340
|
+
const expectedOrigin = new URL(this.iframeOrigin).origin
|
|
341
|
+
if (event.origin !== expectedOrigin) {
|
|
342
|
+
console.warn(
|
|
343
|
+
"[SwarmIdClient] Rejected message from unauthorized origin:",
|
|
344
|
+
event.origin,
|
|
345
|
+
)
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Parse and validate message
|
|
350
|
+
let message: IframeToParentMessage
|
|
351
|
+
try {
|
|
352
|
+
message = IframeToParentMessageSchema.parse(event.data)
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.warn(
|
|
355
|
+
"[SwarmIdClient] Invalid message format:",
|
|
356
|
+
event.data,
|
|
357
|
+
error,
|
|
358
|
+
)
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.handleIframeMessage(message)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
window.addEventListener("message", this.messageListener)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Handle messages from iframe
|
|
370
|
+
*/
|
|
371
|
+
private handleIframeMessage(message: IframeToParentMessage): void {
|
|
372
|
+
switch (message.type) {
|
|
373
|
+
case "proxyReady":
|
|
374
|
+
this.ready = true
|
|
375
|
+
if (this.readyResolve) {
|
|
376
|
+
this.readyResolve()
|
|
377
|
+
}
|
|
378
|
+
// Always show iframe - it will display login or disconnect button
|
|
379
|
+
if (this.iframe) {
|
|
380
|
+
this.iframe.style.display = "block"
|
|
381
|
+
}
|
|
382
|
+
if (this.onAuthChange) {
|
|
383
|
+
this.onAuthChange(message.authenticated)
|
|
384
|
+
}
|
|
385
|
+
break
|
|
386
|
+
|
|
387
|
+
case "authStatusResponse":
|
|
388
|
+
// Always show iframe - it will display login or disconnect button
|
|
389
|
+
if (this.iframe) {
|
|
390
|
+
this.iframe.style.display = "block"
|
|
391
|
+
}
|
|
392
|
+
if (this.onAuthChange) {
|
|
393
|
+
this.onAuthChange(message.authenticated)
|
|
394
|
+
}
|
|
395
|
+
// Handle as response if there's a matching request
|
|
396
|
+
if ("requestId" in message) {
|
|
397
|
+
const pending = this.pendingRequests.get(message.requestId)
|
|
398
|
+
if (pending) {
|
|
399
|
+
clearTimeout(pending.timeoutId)
|
|
400
|
+
this.pendingRequests.delete(message.requestId)
|
|
401
|
+
pending.resolve(message)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
break
|
|
405
|
+
|
|
406
|
+
case "authSuccess":
|
|
407
|
+
// Keep iframe visible - it will now show disconnect button
|
|
408
|
+
if (this.iframe) {
|
|
409
|
+
this.iframe.style.display = "block"
|
|
410
|
+
}
|
|
411
|
+
if (this.onAuthChange) {
|
|
412
|
+
this.onAuthChange(true)
|
|
413
|
+
}
|
|
414
|
+
break
|
|
415
|
+
|
|
416
|
+
case "initError":
|
|
417
|
+
// Initialization error from proxy (e.g., origin validation failed)
|
|
418
|
+
console.error(
|
|
419
|
+
"[SwarmIdClient] Proxy initialization error:",
|
|
420
|
+
message.error,
|
|
421
|
+
)
|
|
422
|
+
if (this.readyReject) {
|
|
423
|
+
this.readyReject(new Error(message.error))
|
|
424
|
+
}
|
|
425
|
+
break
|
|
426
|
+
|
|
427
|
+
case "disconnectResponse":
|
|
428
|
+
// Handle disconnect response
|
|
429
|
+
if (this.onAuthChange) {
|
|
430
|
+
this.onAuthChange(false)
|
|
431
|
+
}
|
|
432
|
+
// Handle as response if there's a matching request
|
|
433
|
+
if ("requestId" in message) {
|
|
434
|
+
const pending = this.pendingRequests.get(message.requestId)
|
|
435
|
+
if (pending) {
|
|
436
|
+
clearTimeout(pending.timeoutId)
|
|
437
|
+
this.pendingRequests.delete(message.requestId)
|
|
438
|
+
pending.resolve(message)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
break
|
|
442
|
+
|
|
443
|
+
case "error": {
|
|
444
|
+
const pending = this.pendingRequests.get(message.requestId)
|
|
445
|
+
if (pending) {
|
|
446
|
+
clearTimeout(pending.timeoutId)
|
|
447
|
+
this.pendingRequests.delete(message.requestId)
|
|
448
|
+
pending.reject(new Error(message.error))
|
|
449
|
+
}
|
|
450
|
+
break
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
default:
|
|
454
|
+
// Handle response messages with requestId
|
|
455
|
+
if ("requestId" in message) {
|
|
456
|
+
const pending = this.pendingRequests.get(message.requestId)
|
|
457
|
+
if (pending) {
|
|
458
|
+
clearTimeout(pending.timeoutId)
|
|
459
|
+
this.pendingRequests.delete(message.requestId)
|
|
460
|
+
pending.resolve(message)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Send message to iframe
|
|
468
|
+
*/
|
|
469
|
+
private sendMessage(message: ParentToIframeMessage): void {
|
|
470
|
+
if (!this.iframe || !this.iframe.contentWindow) {
|
|
471
|
+
throw new Error("Iframe not initialized")
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Validate message before sending
|
|
475
|
+
try {
|
|
476
|
+
ParentToIframeMessageSchema.parse(message)
|
|
477
|
+
} catch (error) {
|
|
478
|
+
throw new Error(`Invalid message format: ${error}`)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this.iframe.contentWindow.postMessage(message, this.iframeOrigin)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Send request and wait for response
|
|
486
|
+
*/
|
|
487
|
+
private async sendRequest<
|
|
488
|
+
TResponse,
|
|
489
|
+
TRequest extends ParentToIframeMessage & { requestId: string } =
|
|
490
|
+
ParentToIframeMessage & {
|
|
491
|
+
requestId: string
|
|
492
|
+
},
|
|
493
|
+
>(message: TRequest): Promise<TResponse> {
|
|
494
|
+
return new Promise((resolve, reject) => {
|
|
495
|
+
const timeoutId = setTimeout(() => {
|
|
496
|
+
this.pendingRequests.delete(message.requestId)
|
|
497
|
+
reject(new Error(`Request timeout after ${this.timeout}ms`))
|
|
498
|
+
}, this.timeout)
|
|
499
|
+
|
|
500
|
+
this.pendingRequests.set(message.requestId, {
|
|
501
|
+
resolve,
|
|
502
|
+
reject,
|
|
503
|
+
timeoutId,
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
this.sendMessage(message)
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Generate unique request ID
|
|
512
|
+
*/
|
|
513
|
+
private generateRequestId(): string {
|
|
514
|
+
return `req-${++this.requestIdCounter}-${Date.now()}`
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private normalizeSocIdentifier(
|
|
518
|
+
identifier: Identifier | Uint8Array | string,
|
|
519
|
+
): string {
|
|
520
|
+
if (identifier instanceof Identifier) {
|
|
521
|
+
return identifier.toHex()
|
|
522
|
+
}
|
|
523
|
+
if (identifier instanceof Uint8Array) {
|
|
524
|
+
return uint8ArrayToHex(identifier)
|
|
525
|
+
}
|
|
526
|
+
return identifier
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private normalizeSocKey(key: Uint8Array | string): string {
|
|
530
|
+
if (key instanceof Uint8Array) {
|
|
531
|
+
return uint8ArrayToHex(key)
|
|
532
|
+
}
|
|
533
|
+
return key
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private normalizeFeedTopic(
|
|
537
|
+
topic: Topic | Identifier | Uint8Array | string,
|
|
538
|
+
): string {
|
|
539
|
+
if (topic instanceof Topic) {
|
|
540
|
+
return uint8ArrayToHex(topic.toUint8Array())
|
|
541
|
+
}
|
|
542
|
+
if (topic instanceof Identifier) {
|
|
543
|
+
return topic.toHex()
|
|
544
|
+
}
|
|
545
|
+
if (topic instanceof Uint8Array) {
|
|
546
|
+
return uint8ArrayToHex(topic)
|
|
547
|
+
}
|
|
548
|
+
return topic
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private normalizeReference(reference: Uint8Array | string): string {
|
|
552
|
+
if (reference instanceof Uint8Array) {
|
|
553
|
+
return uint8ArrayToHex(reference)
|
|
554
|
+
}
|
|
555
|
+
return reference
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private normalizeFeedTimestamp(value: bigint | number | string): string {
|
|
559
|
+
if (typeof value === "bigint") {
|
|
560
|
+
return value.toString()
|
|
561
|
+
}
|
|
562
|
+
if (typeof value === "number") {
|
|
563
|
+
return BigInt(Math.floor(value)).toString()
|
|
564
|
+
}
|
|
565
|
+
return value
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private normalizeFeedIndex(value: bigint | number | string): string {
|
|
569
|
+
if (typeof value === "bigint") {
|
|
570
|
+
return value.toString()
|
|
571
|
+
}
|
|
572
|
+
if (typeof value === "number") {
|
|
573
|
+
return BigInt(Math.floor(value)).toString()
|
|
574
|
+
}
|
|
575
|
+
return value
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private normalizePayload(data: Uint8Array | string): Uint8Array {
|
|
579
|
+
if (data instanceof Uint8Array) {
|
|
580
|
+
return data
|
|
581
|
+
}
|
|
582
|
+
return new TextEncoder().encode(data)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private socChunkFromResponse(response: {
|
|
586
|
+
data: Uint8Array
|
|
587
|
+
identifier: string
|
|
588
|
+
signature: string
|
|
589
|
+
span: number
|
|
590
|
+
payload: Uint8Array
|
|
591
|
+
address: Reference
|
|
592
|
+
owner: string
|
|
593
|
+
}): SingleOwnerChunk {
|
|
594
|
+
return {
|
|
595
|
+
data: response.data,
|
|
596
|
+
identifier: response.identifier,
|
|
597
|
+
signature: response.signature,
|
|
598
|
+
span: response.span,
|
|
599
|
+
payload: response.payload,
|
|
600
|
+
address: response.address,
|
|
601
|
+
owner: response.owner,
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Ensure client is initialized
|
|
607
|
+
*/
|
|
608
|
+
private ensureReady(): void {
|
|
609
|
+
if (!this.ready) {
|
|
610
|
+
throw new Error("SwarmIdClient not initialized. Call initialize() first.")
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ============================================================================
|
|
615
|
+
// Authentication Methods
|
|
616
|
+
// ============================================================================
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Returns the authentication iframe element.
|
|
620
|
+
*
|
|
621
|
+
* The iframe displays authentication UI based on the current auth status:
|
|
622
|
+
* - If not authenticated: shows a "Connect" button
|
|
623
|
+
* - If authenticated: shows identity info and a "Disconnect" button
|
|
624
|
+
*
|
|
625
|
+
* The iframe is positioned fixed in the bottom-right corner of the viewport.
|
|
626
|
+
*
|
|
627
|
+
* @returns The iframe element displaying the authentication UI
|
|
628
|
+
* @throws {Error} If the client is not initialized
|
|
629
|
+
* @throws {Error} If the iframe is not available
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* const iframe = client.getAuthIframe()
|
|
634
|
+
* // The iframe is already displayed; this returns a reference to it
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
getAuthIframe(): HTMLIFrameElement {
|
|
638
|
+
this.ensureReady()
|
|
639
|
+
|
|
640
|
+
if (!this.iframe) {
|
|
641
|
+
throw new Error("Iframe not initialized")
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return this.iframe
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Checks the current authentication status with the Swarm ID proxy.
|
|
649
|
+
*
|
|
650
|
+
* @returns A promise resolving to the authentication status object
|
|
651
|
+
* @returns return.authenticated - Whether the user is currently authenticated
|
|
652
|
+
* @returns return.origin - The origin that authenticated (if authenticated)
|
|
653
|
+
* @throws {Error} If the client is not initialized
|
|
654
|
+
* @throws {Error} If the request times out
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* ```typescript
|
|
658
|
+
* const status = await client.checkAuthStatus()
|
|
659
|
+
* if (status.authenticated) {
|
|
660
|
+
* console.log('Authenticated from:', status.origin)
|
|
661
|
+
* }
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
async checkAuthStatus(): Promise<AuthStatus> {
|
|
665
|
+
this.ensureReady()
|
|
666
|
+
const requestId = this.generateRequestId()
|
|
667
|
+
|
|
668
|
+
const response = await this.sendRequest<{
|
|
669
|
+
type: "authStatusResponse"
|
|
670
|
+
requestId: string
|
|
671
|
+
authenticated: boolean
|
|
672
|
+
origin?: string
|
|
673
|
+
}>({
|
|
674
|
+
type: "checkAuth",
|
|
675
|
+
requestId,
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
authenticated: response.authenticated,
|
|
680
|
+
origin: response.origin,
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Gets the current postage batch for the authenticated identity.
|
|
686
|
+
*
|
|
687
|
+
* Returns information about the postage stamp associated with the
|
|
688
|
+
* connected identity, including batch ID, utilization, depth, and TTL.
|
|
689
|
+
*
|
|
690
|
+
* @returns A promise resolving to the PostageBatch or undefined if none is configured
|
|
691
|
+
* @throws {Error} If the client is not initialized
|
|
692
|
+
* @throws {Error} If the request times out
|
|
693
|
+
*
|
|
694
|
+
* @example
|
|
695
|
+
* ```typescript
|
|
696
|
+
* const batch = await client.getPostageBatch()
|
|
697
|
+
* if (batch) {
|
|
698
|
+
* console.log('Batch ID:', batch.batchID)
|
|
699
|
+
* console.log('Utilization:', batch.utilization)
|
|
700
|
+
* console.log('Depth:', batch.depth)
|
|
701
|
+
* console.log('TTL:', batch.batchTTL)
|
|
702
|
+
* } else {
|
|
703
|
+
* console.log('No postage batch configured')
|
|
704
|
+
* }
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
async getPostageBatch(): Promise<PostageBatch | undefined> {
|
|
708
|
+
this.ensureReady()
|
|
709
|
+
const requestId = this.generateRequestId()
|
|
710
|
+
|
|
711
|
+
const response = await this.sendRequest<{
|
|
712
|
+
type: "getPostageBatchResponse"
|
|
713
|
+
requestId: string
|
|
714
|
+
postageBatch?: PostageBatch
|
|
715
|
+
error?: string
|
|
716
|
+
}>({
|
|
717
|
+
type: "getPostageBatch",
|
|
718
|
+
requestId,
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
if (response.error) {
|
|
722
|
+
throw new Error(response.error)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return response.postageBatch
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Disconnects the current session and clears authentication data.
|
|
730
|
+
*
|
|
731
|
+
* After disconnection, the user will need to re-authenticate to perform
|
|
732
|
+
* uploads or access identity-related features. The {@link onAuthChange}
|
|
733
|
+
* callback will be invoked with `false`.
|
|
734
|
+
*
|
|
735
|
+
* @returns A promise that resolves when disconnection is complete
|
|
736
|
+
* @throws {Error} If the client is not initialized
|
|
737
|
+
* @throws {Error} If the disconnect operation fails
|
|
738
|
+
* @throws {Error} If the request times out
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```typescript
|
|
742
|
+
* await client.disconnect()
|
|
743
|
+
* console.log('User logged out')
|
|
744
|
+
* ```
|
|
745
|
+
*/
|
|
746
|
+
async disconnect(): Promise<void> {
|
|
747
|
+
this.ensureReady()
|
|
748
|
+
const requestId = this.generateRequestId()
|
|
749
|
+
|
|
750
|
+
const response = await this.sendRequest<{
|
|
751
|
+
type: "disconnectResponse"
|
|
752
|
+
requestId: string
|
|
753
|
+
success: boolean
|
|
754
|
+
}>({
|
|
755
|
+
type: "disconnect",
|
|
756
|
+
requestId,
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
if (!response.success) {
|
|
760
|
+
throw new Error("Failed to disconnect")
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Notify via auth change callback
|
|
764
|
+
if (this.onAuthChange) {
|
|
765
|
+
this.onAuthChange(false)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Opens the Swarm ID authentication page in a new window.
|
|
771
|
+
*
|
|
772
|
+
* This method creates the same authentication URL as used by the iframe
|
|
773
|
+
* proxy and opens it in a new browser window. The user can authenticate
|
|
774
|
+
* with their Swarm ID, and the resulting authentication will be available
|
|
775
|
+
* to the client when they return.
|
|
776
|
+
*
|
|
777
|
+
* **Browser Compatibility:**
|
|
778
|
+
* - Production (Chrome/Firefox): Works immediately
|
|
779
|
+
* - Localhost (Chrome/Firefox): Works after iframe button grants Storage Access
|
|
780
|
+
* - Safari (any): Download-only mode — auth works, uploads disabled (ITP storage partitioning). Private mode sessions are ephemeral (lost when the private window closes).
|
|
781
|
+
*
|
|
782
|
+
* For localhost development with Chrome/Firefox, click the iframe button first
|
|
783
|
+
* to grant Storage Access. For Safari details, see https://github.com/snaha/swarm-id/issues/167
|
|
784
|
+
*
|
|
785
|
+
* @param options - Configuration options for the connect flow
|
|
786
|
+
* @param options.agent - When true, shows the agent sign-up option on the connect page
|
|
787
|
+
* @throws {Error} If the client is not initialized or the popup fails to open
|
|
788
|
+
*
|
|
789
|
+
* @example
|
|
790
|
+
* ```typescript
|
|
791
|
+
* const client = new SwarmIdClient({ ... })
|
|
792
|
+
* await client.initialize()
|
|
793
|
+
*
|
|
794
|
+
* // Open authentication page
|
|
795
|
+
* await client.connect()
|
|
796
|
+
*
|
|
797
|
+
* // Open with agent sign-up option visible
|
|
798
|
+
* await client.connect({ agent: true })
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
801
|
+
async connect(options: ConnectOptions = {}): Promise<void> {
|
|
802
|
+
this.ensureReady()
|
|
803
|
+
const requestId = this.generateRequestId()
|
|
804
|
+
|
|
805
|
+
const response = await this.sendRequest<{
|
|
806
|
+
type: "connectResponse"
|
|
807
|
+
requestId: string
|
|
808
|
+
success: boolean
|
|
809
|
+
}>({
|
|
810
|
+
type: "connect",
|
|
811
|
+
requestId,
|
|
812
|
+
agent: options.agent,
|
|
813
|
+
popupMode: options.popupMode,
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
if (!response.success) {
|
|
817
|
+
throw new Error("Failed to open authentication popup")
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Retrieves connection information including upload capability and identity details.
|
|
823
|
+
*
|
|
824
|
+
* Use this method to check if the user can upload data and to get
|
|
825
|
+
* information about the currently connected identity.
|
|
826
|
+
*
|
|
827
|
+
* @returns A promise resolving to the connection info object
|
|
828
|
+
* @returns return.canUpload - Whether the user can upload data (has valid postage stamp)
|
|
829
|
+
* @returns return.identity - The connected identity details (if authenticated)
|
|
830
|
+
* @returns return.identity.id - Unique identifier for the identity
|
|
831
|
+
* @returns return.identity.name - Display name of the identity
|
|
832
|
+
* @returns return.identity.address - Ethereum address associated with the identity
|
|
833
|
+
* @throws {Error} If the client is not initialized
|
|
834
|
+
* @throws {Error} If the request times out
|
|
835
|
+
*
|
|
836
|
+
* @example
|
|
837
|
+
* ```typescript
|
|
838
|
+
* const info = await client.getConnectionInfo()
|
|
839
|
+
* if (info.canUpload) {
|
|
840
|
+
* console.log('Ready to upload as:', info.identity?.name)
|
|
841
|
+
* } else {
|
|
842
|
+
* console.log('No postage stamp available')
|
|
843
|
+
* }
|
|
844
|
+
* ```
|
|
845
|
+
*/
|
|
846
|
+
async getConnectionInfo(): Promise<ConnectionInfo> {
|
|
847
|
+
this.ensureReady()
|
|
848
|
+
const requestId = this.generateRequestId()
|
|
849
|
+
|
|
850
|
+
const response = await this.sendRequest<{
|
|
851
|
+
type: "connectionInfoResponse"
|
|
852
|
+
requestId: string
|
|
853
|
+
canUpload: boolean
|
|
854
|
+
storagePartitioned?: boolean
|
|
855
|
+
identity?: { id: string; name: string; address: string }
|
|
856
|
+
}>({
|
|
857
|
+
type: "getConnectionInfo",
|
|
858
|
+
requestId,
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
return {
|
|
862
|
+
canUpload: response.canUpload,
|
|
863
|
+
storagePartitioned: response.storagePartitioned,
|
|
864
|
+
identity: response.identity,
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// ============================================================================
|
|
869
|
+
// Bee Connectivity Methods
|
|
870
|
+
// ============================================================================
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Checks whether the Bee node is reachable.
|
|
874
|
+
*
|
|
875
|
+
* This method never throws an exception. If the client is not initialized,
|
|
876
|
+
* the request times out, or any other error occurs, it returns `false`.
|
|
877
|
+
*
|
|
878
|
+
* @returns A promise resolving to `true` if the Bee node is reachable, `false` otherwise
|
|
879
|
+
*
|
|
880
|
+
* @example
|
|
881
|
+
* ```typescript
|
|
882
|
+
* const connected = await client.isBeeConnected()
|
|
883
|
+
* if (connected) {
|
|
884
|
+
* console.log('Bee node is online')
|
|
885
|
+
* } else {
|
|
886
|
+
* console.log('Bee node is offline')
|
|
887
|
+
* }
|
|
888
|
+
* ```
|
|
889
|
+
*/
|
|
890
|
+
async isBeeConnected(): Promise<boolean> {
|
|
891
|
+
try {
|
|
892
|
+
this.ensureReady()
|
|
893
|
+
const requestId = this.generateRequestId()
|
|
894
|
+
|
|
895
|
+
const response = await this.sendRequest<{
|
|
896
|
+
type: "isConnectedResponse"
|
|
897
|
+
requestId: string
|
|
898
|
+
connected: boolean
|
|
899
|
+
}>({
|
|
900
|
+
type: "isConnected",
|
|
901
|
+
requestId,
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
return response.connected
|
|
905
|
+
} catch {
|
|
906
|
+
return false
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Gets information about the Bee node configuration.
|
|
912
|
+
*
|
|
913
|
+
* This method retrieves the current Bee node's operating mode and feature flags.
|
|
914
|
+
* Use this to determine if deferred uploads are required (dev mode) or if direct
|
|
915
|
+
* uploads are available (production modes).
|
|
916
|
+
*
|
|
917
|
+
* @returns A promise resolving to the node info object
|
|
918
|
+
* @returns return.beeMode - The Bee node operating mode ("dev", "light", "full", "ultra-light")
|
|
919
|
+
* @returns return.chequebookEnabled - Whether the chequebook is enabled
|
|
920
|
+
* @returns return.swapEnabled - Whether SWAP is enabled
|
|
921
|
+
* @throws {Error} If the client is not initialized
|
|
922
|
+
* @throws {Error} If the Bee node is not reachable
|
|
923
|
+
* @throws {Error} If the request times out
|
|
924
|
+
*
|
|
925
|
+
* @example
|
|
926
|
+
* ```typescript
|
|
927
|
+
* const nodeInfo = await client.getNodeInfo()
|
|
928
|
+
* if (nodeInfo.beeMode === 'dev') {
|
|
929
|
+
* // Dev mode requires deferred uploads
|
|
930
|
+
* await client.uploadData(data, { deferred: true })
|
|
931
|
+
* } else {
|
|
932
|
+
* // Production modes can use direct uploads
|
|
933
|
+
* await client.uploadData(data, { deferred: false })
|
|
934
|
+
* }
|
|
935
|
+
* ```
|
|
936
|
+
*/
|
|
937
|
+
async getNodeInfo(): Promise<{
|
|
938
|
+
beeMode: string
|
|
939
|
+
chequebookEnabled: boolean
|
|
940
|
+
swapEnabled: boolean
|
|
941
|
+
}> {
|
|
942
|
+
this.ensureReady()
|
|
943
|
+
const requestId = this.generateRequestId()
|
|
944
|
+
|
|
945
|
+
const response = await this.sendRequest<{
|
|
946
|
+
type: "getNodeInfoResponse"
|
|
947
|
+
requestId: string
|
|
948
|
+
beeMode: string
|
|
949
|
+
chequebookEnabled: boolean
|
|
950
|
+
swapEnabled: boolean
|
|
951
|
+
}>({
|
|
952
|
+
type: "getNodeInfo",
|
|
953
|
+
requestId,
|
|
954
|
+
})
|
|
955
|
+
|
|
956
|
+
return {
|
|
957
|
+
beeMode: response.beeMode,
|
|
958
|
+
chequebookEnabled: response.chequebookEnabled,
|
|
959
|
+
swapEnabled: response.swapEnabled,
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// ============================================================================
|
|
964
|
+
// Data Upload/Download Methods
|
|
965
|
+
// ============================================================================
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Uploads raw binary data to the Swarm network.
|
|
969
|
+
*
|
|
970
|
+
* The data is uploaded using the authenticated user's postage stamp.
|
|
971
|
+
* Progress can be tracked via the optional callback.
|
|
972
|
+
*
|
|
973
|
+
* @param data - The binary data to upload as a Uint8Array
|
|
974
|
+
* @param options - Optional upload configuration
|
|
975
|
+
* @param options.pin - Whether to pin the data locally (defaults to false)
|
|
976
|
+
* @param options.encrypt - Whether to encrypt the data (defaults to false)
|
|
977
|
+
* @param options.tag - Tag ID for tracking upload progress
|
|
978
|
+
* @param options.deferred - Whether to use deferred upload (defaults to false)
|
|
979
|
+
* @param options.redundancyLevel - Redundancy level from 0-4 for data availability
|
|
980
|
+
* @param options.onProgress - Optional callback for tracking upload progress
|
|
981
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
982
|
+
* @returns A promise resolving to the upload result
|
|
983
|
+
* @returns return.reference - The Swarm reference (hash) of the uploaded data
|
|
984
|
+
* @returns return.tagUid - The tag UID if a tag was created
|
|
985
|
+
* @throws {Error} If the client is not initialized
|
|
986
|
+
* @throws {Error} If the user is not authenticated or cannot upload
|
|
987
|
+
* @throws {Error} If the request times out
|
|
988
|
+
*
|
|
989
|
+
* @example
|
|
990
|
+
* ```typescript
|
|
991
|
+
* const data = new TextEncoder().encode('Hello, Swarm!')
|
|
992
|
+
* const result = await client.uploadData(data, {
|
|
993
|
+
* encrypt: true,
|
|
994
|
+
* onProgress: (progress) => {
|
|
995
|
+
* console.log(`Progress: ${progress.processed}/${progress.total}`)
|
|
996
|
+
* },
|
|
997
|
+
* })
|
|
998
|
+
* console.log('Reference:', result.reference)
|
|
999
|
+
* ```
|
|
1000
|
+
*/
|
|
1001
|
+
async uploadData(
|
|
1002
|
+
data: Uint8Array,
|
|
1003
|
+
options?: UploadOptions,
|
|
1004
|
+
requestOptions?: RequestOptions,
|
|
1005
|
+
): Promise<UploadResult> {
|
|
1006
|
+
this.ensureReady()
|
|
1007
|
+
const requestId = this.generateRequestId()
|
|
1008
|
+
const { onProgress, ...serializableOptions } = options ?? {}
|
|
1009
|
+
|
|
1010
|
+
// Setup progress listener if callback provided
|
|
1011
|
+
let progressListener: ((event: MessageEvent) => void) | undefined
|
|
1012
|
+
if (onProgress) {
|
|
1013
|
+
progressListener = (event: MessageEvent) => {
|
|
1014
|
+
if (event.origin !== new URL(this.iframeOrigin).origin) return
|
|
1015
|
+
|
|
1016
|
+
try {
|
|
1017
|
+
const message = IframeToParentMessageSchema.parse(event.data)
|
|
1018
|
+
if (
|
|
1019
|
+
message.type === "uploadProgress" &&
|
|
1020
|
+
message.requestId === requestId
|
|
1021
|
+
) {
|
|
1022
|
+
onProgress({
|
|
1023
|
+
total: message.total,
|
|
1024
|
+
processed: message.processed,
|
|
1025
|
+
})
|
|
1026
|
+
}
|
|
1027
|
+
} catch {
|
|
1028
|
+
// Ignore invalid messages
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
window.addEventListener("message", progressListener)
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
try {
|
|
1035
|
+
const response = await this.sendRequest<{
|
|
1036
|
+
type: "uploadDataResponse"
|
|
1037
|
+
requestId: string
|
|
1038
|
+
reference: Reference
|
|
1039
|
+
tagUid?: number
|
|
1040
|
+
}>({
|
|
1041
|
+
type: "uploadData",
|
|
1042
|
+
requestId,
|
|
1043
|
+
data: new Uint8Array(data),
|
|
1044
|
+
options: serializableOptions,
|
|
1045
|
+
requestOptions,
|
|
1046
|
+
enableProgress: !!onProgress,
|
|
1047
|
+
})
|
|
1048
|
+
|
|
1049
|
+
return {
|
|
1050
|
+
reference: response.reference,
|
|
1051
|
+
tagUid: response.tagUid,
|
|
1052
|
+
}
|
|
1053
|
+
} finally {
|
|
1054
|
+
// Clean up progress listener
|
|
1055
|
+
if (progressListener) {
|
|
1056
|
+
window.removeEventListener("message", progressListener)
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Downloads raw binary data from the Swarm network.
|
|
1063
|
+
*
|
|
1064
|
+
* @param reference - The Swarm reference (hash) of the data to download.
|
|
1065
|
+
* Can be 64 hex chars (32 bytes) or 128 hex chars (64 bytes for encrypted)
|
|
1066
|
+
* @param options - Optional download configuration
|
|
1067
|
+
* @param options.redundancyStrategy - Strategy for handling redundancy (0-3)
|
|
1068
|
+
* @param options.fallback - Whether to use fallback retrieval
|
|
1069
|
+
* @param options.timeoutMs - Download timeout in milliseconds
|
|
1070
|
+
* @param options.actPublisher - ACT publisher for encrypted content
|
|
1071
|
+
* @param options.actHistoryAddress - ACT history address for encrypted content
|
|
1072
|
+
* @param options.actTimestamp - ACT timestamp for encrypted content
|
|
1073
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1074
|
+
* @returns A promise resolving to the downloaded data as a Uint8Array
|
|
1075
|
+
* @throws {Error} If the client is not initialized
|
|
1076
|
+
* @throws {Error} If the reference is not found
|
|
1077
|
+
* @throws {Error} If the request times out
|
|
1078
|
+
*
|
|
1079
|
+
* @example
|
|
1080
|
+
* ```typescript
|
|
1081
|
+
* const data = await client.downloadData('a1b2c3...') // 64 char hex reference
|
|
1082
|
+
* const text = new TextDecoder().decode(data)
|
|
1083
|
+
* console.log('Downloaded:', text)
|
|
1084
|
+
* ```
|
|
1085
|
+
*/
|
|
1086
|
+
async downloadData(
|
|
1087
|
+
reference: Reference,
|
|
1088
|
+
options?: DownloadOptions,
|
|
1089
|
+
requestOptions?: RequestOptions,
|
|
1090
|
+
): Promise<Uint8Array> {
|
|
1091
|
+
this.ensureReady()
|
|
1092
|
+
const requestId = this.generateRequestId()
|
|
1093
|
+
|
|
1094
|
+
const response = await this.sendRequest<{
|
|
1095
|
+
type: "downloadDataResponse"
|
|
1096
|
+
requestId: string
|
|
1097
|
+
data: Uint8Array
|
|
1098
|
+
}>({
|
|
1099
|
+
type: "downloadData",
|
|
1100
|
+
requestId,
|
|
1101
|
+
reference,
|
|
1102
|
+
options,
|
|
1103
|
+
requestOptions,
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
return response.data
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// ============================================================================
|
|
1110
|
+
// File Upload/Download Methods
|
|
1111
|
+
// ============================================================================
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Uploads a file to the Swarm network.
|
|
1115
|
+
*
|
|
1116
|
+
* Accepts either a File object (from file input) or raw Uint8Array data.
|
|
1117
|
+
* When using a File object, the filename is automatically extracted unless
|
|
1118
|
+
* explicitly overridden.
|
|
1119
|
+
*
|
|
1120
|
+
* @param file - The file to upload (File object or Uint8Array)
|
|
1121
|
+
* @param name - Optional filename (extracted from File object if not provided)
|
|
1122
|
+
* @param options - Optional upload configuration
|
|
1123
|
+
* @param options.pin - Whether to pin the file locally (defaults to false)
|
|
1124
|
+
* @param options.encrypt - Whether to encrypt the file (defaults to false)
|
|
1125
|
+
* @param options.tag - Tag ID for tracking upload progress
|
|
1126
|
+
* @param options.deferred - Whether to use deferred upload (defaults to false)
|
|
1127
|
+
* @param options.redundancyLevel - Redundancy level from 0-4 for data availability
|
|
1128
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1129
|
+
* @returns A promise resolving to the upload result
|
|
1130
|
+
* @returns return.reference - The Swarm reference (hash) of the uploaded file
|
|
1131
|
+
* @returns return.tagUid - The tag UID if a tag was created
|
|
1132
|
+
* @throws {Error} If the client is not initialized
|
|
1133
|
+
* @throws {Error} If the user is not authenticated or cannot upload
|
|
1134
|
+
* @throws {Error} If the request times out
|
|
1135
|
+
*
|
|
1136
|
+
* @example
|
|
1137
|
+
* ```typescript
|
|
1138
|
+
* // From file input
|
|
1139
|
+
* const fileInput = document.querySelector('input[type="file"]')
|
|
1140
|
+
* const file = fileInput.files[0]
|
|
1141
|
+
* const result = await client.uploadFile(file)
|
|
1142
|
+
*
|
|
1143
|
+
* // From Uint8Array with custom name
|
|
1144
|
+
* const data = new Uint8Array([...])
|
|
1145
|
+
* const result = await client.uploadFile(data, 'document.pdf')
|
|
1146
|
+
* ```
|
|
1147
|
+
*/
|
|
1148
|
+
async uploadFile(
|
|
1149
|
+
file: File | Uint8Array,
|
|
1150
|
+
name?: string,
|
|
1151
|
+
options?: UploadOptions,
|
|
1152
|
+
requestOptions?: RequestOptions,
|
|
1153
|
+
): Promise<UploadResult> {
|
|
1154
|
+
this.ensureReady()
|
|
1155
|
+
const requestId = this.generateRequestId()
|
|
1156
|
+
const { onProgress: _onProgress, ...serializableOptions } = options ?? {}
|
|
1157
|
+
|
|
1158
|
+
let data: Uint8Array<ArrayBuffer>
|
|
1159
|
+
let fileName: string | undefined = name
|
|
1160
|
+
|
|
1161
|
+
if (file instanceof File) {
|
|
1162
|
+
const buffer = await file.arrayBuffer()
|
|
1163
|
+
data = new Uint8Array(buffer)
|
|
1164
|
+
fileName = fileName || file.name
|
|
1165
|
+
} else {
|
|
1166
|
+
data = new Uint8Array(
|
|
1167
|
+
file.buffer.slice(0),
|
|
1168
|
+
file.byteOffset,
|
|
1169
|
+
file.byteLength,
|
|
1170
|
+
)
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const response = await this.sendRequest<{
|
|
1174
|
+
type: "uploadFileResponse"
|
|
1175
|
+
requestId: string
|
|
1176
|
+
reference: Reference
|
|
1177
|
+
tagUid?: number
|
|
1178
|
+
}>({
|
|
1179
|
+
type: "uploadFile",
|
|
1180
|
+
requestId,
|
|
1181
|
+
data,
|
|
1182
|
+
name: fileName,
|
|
1183
|
+
options: serializableOptions,
|
|
1184
|
+
requestOptions,
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
return {
|
|
1188
|
+
reference: response.reference,
|
|
1189
|
+
tagUid: response.tagUid,
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Downloads a file from the Swarm network.
|
|
1195
|
+
*
|
|
1196
|
+
* Returns both the file data and its original filename (if available).
|
|
1197
|
+
* For manifest references, an optional path can be specified to retrieve
|
|
1198
|
+
* a specific file from the manifest.
|
|
1199
|
+
*
|
|
1200
|
+
* @param reference - The Swarm reference (hash) of the file to download
|
|
1201
|
+
* @param path - Optional path within a manifest to retrieve a specific file
|
|
1202
|
+
* @param options - Optional download configuration
|
|
1203
|
+
* @param options.redundancyStrategy - Strategy for handling redundancy (0-3)
|
|
1204
|
+
* @param options.fallback - Whether to use fallback retrieval
|
|
1205
|
+
* @param options.timeoutMs - Download timeout in milliseconds
|
|
1206
|
+
* @param options.actPublisher - ACT publisher for encrypted content
|
|
1207
|
+
* @param options.actHistoryAddress - ACT history address for encrypted content
|
|
1208
|
+
* @param options.actTimestamp - ACT timestamp for encrypted content
|
|
1209
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1210
|
+
* @returns A promise resolving to the file data object
|
|
1211
|
+
* @returns return.name - The filename
|
|
1212
|
+
* @returns return.data - The file contents as a Uint8Array
|
|
1213
|
+
* @throws {Error} If the client is not initialized
|
|
1214
|
+
* @throws {Error} If the reference is not found
|
|
1215
|
+
* @throws {Error} If the request times out
|
|
1216
|
+
*
|
|
1217
|
+
* @example
|
|
1218
|
+
* ```typescript
|
|
1219
|
+
* const file = await client.downloadFile('a1b2c3...')
|
|
1220
|
+
* console.log('Filename:', file.name)
|
|
1221
|
+
*
|
|
1222
|
+
* // Create download link
|
|
1223
|
+
* const blob = new Blob([file.data])
|
|
1224
|
+
* const url = URL.createObjectURL(blob)
|
|
1225
|
+
* ```
|
|
1226
|
+
*/
|
|
1227
|
+
async downloadFile(
|
|
1228
|
+
reference: Reference,
|
|
1229
|
+
path?: string,
|
|
1230
|
+
options?: DownloadOptions,
|
|
1231
|
+
requestOptions?: RequestOptions,
|
|
1232
|
+
): Promise<FileData> {
|
|
1233
|
+
this.ensureReady()
|
|
1234
|
+
const requestId = this.generateRequestId()
|
|
1235
|
+
|
|
1236
|
+
const response = await this.sendRequest<{
|
|
1237
|
+
type: "downloadFileResponse"
|
|
1238
|
+
requestId: string
|
|
1239
|
+
name: string
|
|
1240
|
+
data: number[]
|
|
1241
|
+
}>({
|
|
1242
|
+
type: "downloadFile",
|
|
1243
|
+
requestId,
|
|
1244
|
+
reference,
|
|
1245
|
+
path,
|
|
1246
|
+
options,
|
|
1247
|
+
requestOptions,
|
|
1248
|
+
})
|
|
1249
|
+
|
|
1250
|
+
return {
|
|
1251
|
+
name: response.name,
|
|
1252
|
+
data: new Uint8Array(response.data),
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// ============================================================================
|
|
1257
|
+
// Chunk Upload/Download Methods
|
|
1258
|
+
// ============================================================================
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Uploads a single chunk to the Swarm network.
|
|
1262
|
+
*
|
|
1263
|
+
* Chunks are the fundamental unit of storage in Swarm (4KB each).
|
|
1264
|
+
* This method is useful for low-level operations or when implementing
|
|
1265
|
+
* custom chunking strategies.
|
|
1266
|
+
*
|
|
1267
|
+
* @param data - The chunk data to upload (should be exactly 4KB for optimal storage)
|
|
1268
|
+
* @param options - Optional upload configuration
|
|
1269
|
+
* @param options.pin - Whether to pin the chunk locally (defaults to false)
|
|
1270
|
+
* @param options.encrypt - Whether to encrypt the chunk (defaults to false)
|
|
1271
|
+
* @param options.tag - Tag ID for tracking upload progress
|
|
1272
|
+
* @param options.deferred - Whether to use deferred upload (defaults to false)
|
|
1273
|
+
* @param options.redundancyLevel - Redundancy level from 0-4 for data availability
|
|
1274
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1275
|
+
* @returns A promise resolving to the upload result
|
|
1276
|
+
* @returns return.reference - The Swarm reference (hash) of the uploaded chunk
|
|
1277
|
+
* @throws {Error} If the client is not initialized
|
|
1278
|
+
* @throws {Error} If the user is not authenticated or cannot upload
|
|
1279
|
+
* @throws {Error} If the request times out
|
|
1280
|
+
*
|
|
1281
|
+
* @example
|
|
1282
|
+
* ```typescript
|
|
1283
|
+
* const chunk = new Uint8Array(4096) // 4KB chunk
|
|
1284
|
+
* chunk.fill(0x42) // Fill with data
|
|
1285
|
+
* const result = await client.uploadChunk(chunk)
|
|
1286
|
+
* console.log('Chunk reference:', result.reference)
|
|
1287
|
+
* ```
|
|
1288
|
+
*/
|
|
1289
|
+
async uploadChunk(
|
|
1290
|
+
data: Uint8Array,
|
|
1291
|
+
options?: UploadOptions,
|
|
1292
|
+
requestOptions?: RequestOptions,
|
|
1293
|
+
): Promise<UploadResult> {
|
|
1294
|
+
this.ensureReady()
|
|
1295
|
+
const requestId = this.generateRequestId()
|
|
1296
|
+
const { onProgress: _onProgress, ...serializableOptions } = options ?? {}
|
|
1297
|
+
|
|
1298
|
+
const response = await this.sendRequest<{
|
|
1299
|
+
type: "uploadChunkResponse"
|
|
1300
|
+
requestId: string
|
|
1301
|
+
reference: Reference
|
|
1302
|
+
}>({
|
|
1303
|
+
type: "uploadChunk",
|
|
1304
|
+
requestId,
|
|
1305
|
+
data: data as Uint8Array,
|
|
1306
|
+
options: serializableOptions,
|
|
1307
|
+
requestOptions,
|
|
1308
|
+
})
|
|
1309
|
+
|
|
1310
|
+
return {
|
|
1311
|
+
reference: response.reference,
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Downloads a single chunk from the Swarm network.
|
|
1317
|
+
*
|
|
1318
|
+
* Retrieves a chunk by its reference hash. This method is useful for
|
|
1319
|
+
* low-level operations or when implementing custom retrieval strategies.
|
|
1320
|
+
*
|
|
1321
|
+
* @param reference - The Swarm reference (hash) of the chunk to download
|
|
1322
|
+
* @param options - Optional download configuration
|
|
1323
|
+
* @param options.redundancyStrategy - Strategy for handling redundancy (0-3)
|
|
1324
|
+
* @param options.fallback - Whether to use fallback retrieval
|
|
1325
|
+
* @param options.timeoutMs - Download timeout in milliseconds
|
|
1326
|
+
* @param options.actPublisher - ACT publisher for encrypted content
|
|
1327
|
+
* @param options.actHistoryAddress - ACT history address for encrypted content
|
|
1328
|
+
* @param options.actTimestamp - ACT timestamp for encrypted content
|
|
1329
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1330
|
+
* @returns A promise resolving to the chunk data as a Uint8Array
|
|
1331
|
+
* @throws {Error} If the client is not initialized
|
|
1332
|
+
* @throws {Error} If the reference is not found
|
|
1333
|
+
* @throws {Error} If the request times out
|
|
1334
|
+
*
|
|
1335
|
+
* @example
|
|
1336
|
+
* ```typescript
|
|
1337
|
+
* const chunk = await client.downloadChunk('a1b2c3...')
|
|
1338
|
+
* console.log('Chunk size:', chunk.length)
|
|
1339
|
+
* ```
|
|
1340
|
+
*/
|
|
1341
|
+
async downloadChunk(
|
|
1342
|
+
reference: Reference,
|
|
1343
|
+
options?: DownloadOptions,
|
|
1344
|
+
requestOptions?: RequestOptions,
|
|
1345
|
+
): Promise<Uint8Array> {
|
|
1346
|
+
this.ensureReady()
|
|
1347
|
+
const requestId = this.generateRequestId()
|
|
1348
|
+
|
|
1349
|
+
const response = await this.sendRequest<{
|
|
1350
|
+
type: "downloadChunkResponse"
|
|
1351
|
+
requestId: string
|
|
1352
|
+
data: number[]
|
|
1353
|
+
}>({
|
|
1354
|
+
type: "downloadChunk",
|
|
1355
|
+
requestId,
|
|
1356
|
+
reference,
|
|
1357
|
+
options,
|
|
1358
|
+
requestOptions,
|
|
1359
|
+
})
|
|
1360
|
+
|
|
1361
|
+
return new Uint8Array(response.data)
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// ============================================================================
|
|
1365
|
+
// SOC (Single Owner Chunk) Methods
|
|
1366
|
+
// ============================================================================
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Returns an object for reading single owner chunks (SOC).
|
|
1370
|
+
*
|
|
1371
|
+
* @param ownerAddress - Ethereum address of the SOC owner
|
|
1372
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1373
|
+
* @returns SOCReader with `download` (encrypted) and `rawDownload` (unencrypted)
|
|
1374
|
+
* @throws {Error} If the client is not initialized
|
|
1375
|
+
* @throws {Error} If the request times out
|
|
1376
|
+
*
|
|
1377
|
+
* @example
|
|
1378
|
+
* ```typescript
|
|
1379
|
+
* const reader = client.makeSOCReader(owner)
|
|
1380
|
+
* const soc = await reader.download(identifier, encryptionKey)
|
|
1381
|
+
* console.log('Payload:', new TextDecoder().decode(soc.payload))
|
|
1382
|
+
* ```
|
|
1383
|
+
*/
|
|
1384
|
+
makeSOCReader(
|
|
1385
|
+
ownerAddress: EthAddress | Uint8Array | string,
|
|
1386
|
+
requestOptions?: RequestOptions,
|
|
1387
|
+
): SOCReader {
|
|
1388
|
+
this.ensureReady()
|
|
1389
|
+
const owner = new EthAddress(ownerAddress).toHex()
|
|
1390
|
+
|
|
1391
|
+
const sendSocDownload = async (
|
|
1392
|
+
identifier: Identifier | Uint8Array | string,
|
|
1393
|
+
encryptionKey: Uint8Array | string,
|
|
1394
|
+
): Promise<SingleOwnerChunk> => {
|
|
1395
|
+
const requestId = this.generateRequestId()
|
|
1396
|
+
const response = await this.sendRequest<
|
|
1397
|
+
SocDownloadResponseMessage,
|
|
1398
|
+
SocDownloadMessage
|
|
1399
|
+
>({
|
|
1400
|
+
type: "socDownload",
|
|
1401
|
+
requestId,
|
|
1402
|
+
owner,
|
|
1403
|
+
identifier: this.normalizeSocIdentifier(identifier),
|
|
1404
|
+
encryptionKey: this.normalizeSocKey(encryptionKey),
|
|
1405
|
+
requestOptions,
|
|
1406
|
+
})
|
|
1407
|
+
|
|
1408
|
+
return this.socChunkFromResponse(response)
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
const sendRawSocDownload = async (
|
|
1412
|
+
identifier: Identifier | Uint8Array | string,
|
|
1413
|
+
encryptionKey?: Uint8Array | string,
|
|
1414
|
+
): Promise<SingleOwnerChunk> => {
|
|
1415
|
+
const requestId = this.generateRequestId()
|
|
1416
|
+
const response = await this.sendRequest<
|
|
1417
|
+
SocRawDownloadResponseMessage,
|
|
1418
|
+
SocRawDownloadMessage
|
|
1419
|
+
>({
|
|
1420
|
+
type: "socRawDownload",
|
|
1421
|
+
requestId,
|
|
1422
|
+
owner,
|
|
1423
|
+
identifier: this.normalizeSocIdentifier(identifier),
|
|
1424
|
+
encryptionKey: encryptionKey
|
|
1425
|
+
? this.normalizeSocKey(encryptionKey)
|
|
1426
|
+
: undefined,
|
|
1427
|
+
requestOptions,
|
|
1428
|
+
})
|
|
1429
|
+
|
|
1430
|
+
return this.socChunkFromResponse(response)
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
return {
|
|
1434
|
+
getOwner: async () => owner,
|
|
1435
|
+
rawDownload: (identifier, encryptionKey) =>
|
|
1436
|
+
sendRawSocDownload(identifier, encryptionKey),
|
|
1437
|
+
download: (identifier, encryptionKey) =>
|
|
1438
|
+
sendSocDownload(identifier, encryptionKey),
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Returns an object for reading and writing single owner chunks (SOC).
|
|
1444
|
+
*
|
|
1445
|
+
* Uploads are encrypted by default. Use `rawUpload` for unencrypted SOCs.
|
|
1446
|
+
*
|
|
1447
|
+
* @param signer - Optional SOC signer private key. If omitted, the proxy uses the app signer.
|
|
1448
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1449
|
+
* @returns SOCWriter with `upload`, `rawUpload`, `download`, and `rawDownload`
|
|
1450
|
+
* @throws {Error} If the client is not initialized
|
|
1451
|
+
* @throws {Error} If the request times out
|
|
1452
|
+
*
|
|
1453
|
+
* @example
|
|
1454
|
+
* ```typescript
|
|
1455
|
+
* const writer = client.makeSOCWriter()
|
|
1456
|
+
* const upload = await writer.upload(identifier, payload)
|
|
1457
|
+
* const soc = await writer.download(identifier, upload.encryptionKey)
|
|
1458
|
+
* ```
|
|
1459
|
+
*/
|
|
1460
|
+
makeSOCWriter(
|
|
1461
|
+
signer?: PrivateKey | Uint8Array | string,
|
|
1462
|
+
requestOptions?: RequestOptions,
|
|
1463
|
+
): SOCWriter {
|
|
1464
|
+
this.ensureReady()
|
|
1465
|
+
const signerObj = signer ? new PrivateKey(signer) : undefined
|
|
1466
|
+
const signerKey = signerObj ? signerObj.toHex() : undefined
|
|
1467
|
+
let owner: string | undefined = signerObj
|
|
1468
|
+
? signerObj.publicKey().address().toHex()
|
|
1469
|
+
: undefined
|
|
1470
|
+
|
|
1471
|
+
const resolveOwner = async (): Promise<string> => {
|
|
1472
|
+
if (owner) {
|
|
1473
|
+
return owner
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const requestId = this.generateRequestId()
|
|
1477
|
+
const response = await this.sendRequest<
|
|
1478
|
+
SocGetOwnerResponseMessage,
|
|
1479
|
+
SocGetOwnerMessage
|
|
1480
|
+
>({
|
|
1481
|
+
type: "socGetOwner",
|
|
1482
|
+
requestId,
|
|
1483
|
+
})
|
|
1484
|
+
|
|
1485
|
+
owner = response.owner
|
|
1486
|
+
return owner
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const sendSocDownload = async (
|
|
1490
|
+
identifier: Identifier | Uint8Array | string,
|
|
1491
|
+
encryptionKey: Uint8Array | string,
|
|
1492
|
+
): Promise<SingleOwnerChunk> => {
|
|
1493
|
+
const requestId = this.generateRequestId()
|
|
1494
|
+
|
|
1495
|
+
const response = await this.sendRequest<
|
|
1496
|
+
SocDownloadResponseMessage,
|
|
1497
|
+
SocDownloadMessage
|
|
1498
|
+
>({
|
|
1499
|
+
type: "socDownload",
|
|
1500
|
+
requestId,
|
|
1501
|
+
owner,
|
|
1502
|
+
identifier: this.normalizeSocIdentifier(identifier),
|
|
1503
|
+
encryptionKey: this.normalizeSocKey(encryptionKey),
|
|
1504
|
+
requestOptions,
|
|
1505
|
+
})
|
|
1506
|
+
|
|
1507
|
+
return this.socChunkFromResponse(response)
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const sendRawSocDownload = async (
|
|
1511
|
+
identifier: Identifier | Uint8Array | string,
|
|
1512
|
+
encryptionKey?: Uint8Array | string,
|
|
1513
|
+
): Promise<SingleOwnerChunk> => {
|
|
1514
|
+
const requestId = this.generateRequestId()
|
|
1515
|
+
|
|
1516
|
+
const response = await this.sendRequest<
|
|
1517
|
+
SocRawDownloadResponseMessage,
|
|
1518
|
+
SocRawDownloadMessage
|
|
1519
|
+
>({
|
|
1520
|
+
type: "socRawDownload",
|
|
1521
|
+
requestId,
|
|
1522
|
+
owner,
|
|
1523
|
+
identifier: this.normalizeSocIdentifier(identifier),
|
|
1524
|
+
encryptionKey: encryptionKey
|
|
1525
|
+
? this.normalizeSocKey(encryptionKey)
|
|
1526
|
+
: undefined,
|
|
1527
|
+
requestOptions,
|
|
1528
|
+
})
|
|
1529
|
+
|
|
1530
|
+
return this.socChunkFromResponse(response)
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
const sendSocUpload = async (
|
|
1534
|
+
identifier: Identifier | Uint8Array | string,
|
|
1535
|
+
data: Uint8Array,
|
|
1536
|
+
options?: UploadOptions,
|
|
1537
|
+
): Promise<SocUploadResult> => {
|
|
1538
|
+
const requestId = this.generateRequestId()
|
|
1539
|
+
const response = await this.sendRequest<
|
|
1540
|
+
SocUploadResponseMessage,
|
|
1541
|
+
SocUploadMessage
|
|
1542
|
+
>({
|
|
1543
|
+
type: "socUpload",
|
|
1544
|
+
requestId,
|
|
1545
|
+
identifier: this.normalizeSocIdentifier(identifier),
|
|
1546
|
+
data: new Uint8Array(data),
|
|
1547
|
+
signer: signerKey,
|
|
1548
|
+
options,
|
|
1549
|
+
requestOptions,
|
|
1550
|
+
})
|
|
1551
|
+
|
|
1552
|
+
owner = response.owner
|
|
1553
|
+
|
|
1554
|
+
return {
|
|
1555
|
+
reference: response.reference,
|
|
1556
|
+
tagUid: response.tagUid,
|
|
1557
|
+
encryptionKey: response.encryptionKey,
|
|
1558
|
+
owner: response.owner,
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
const sendRawSocUpload = async (
|
|
1563
|
+
identifier: Identifier | Uint8Array | string,
|
|
1564
|
+
data: Uint8Array,
|
|
1565
|
+
options?: UploadOptions,
|
|
1566
|
+
): Promise<SocRawUploadResult> => {
|
|
1567
|
+
const requestId = this.generateRequestId()
|
|
1568
|
+
const response = await this.sendRequest<
|
|
1569
|
+
SocRawUploadResponseMessage,
|
|
1570
|
+
SocRawUploadMessage
|
|
1571
|
+
>({
|
|
1572
|
+
type: "socRawUpload",
|
|
1573
|
+
requestId,
|
|
1574
|
+
identifier: this.normalizeSocIdentifier(identifier),
|
|
1575
|
+
data: new Uint8Array(data),
|
|
1576
|
+
signer: signerKey,
|
|
1577
|
+
options,
|
|
1578
|
+
requestOptions,
|
|
1579
|
+
})
|
|
1580
|
+
|
|
1581
|
+
owner = response.owner
|
|
1582
|
+
|
|
1583
|
+
return {
|
|
1584
|
+
reference: response.reference,
|
|
1585
|
+
tagUid: response.tagUid,
|
|
1586
|
+
encryptionKey: response.encryptionKey,
|
|
1587
|
+
owner: response.owner,
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
return {
|
|
1592
|
+
getOwner: resolveOwner,
|
|
1593
|
+
rawDownload: (identifier, encryptionKey) =>
|
|
1594
|
+
sendRawSocDownload(identifier, encryptionKey),
|
|
1595
|
+
download: (identifier, encryptionKey) =>
|
|
1596
|
+
sendSocDownload(identifier, encryptionKey),
|
|
1597
|
+
upload: (identifier, data, options) =>
|
|
1598
|
+
sendSocUpload(identifier, data, options),
|
|
1599
|
+
rawUpload: (identifier, data, options) =>
|
|
1600
|
+
sendRawSocUpload(identifier, data, options),
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
/**
|
|
1605
|
+
* Returns an object for reading epoch-based feeds.
|
|
1606
|
+
*
|
|
1607
|
+
* @param options - Feed reader options
|
|
1608
|
+
* @param options.topic - Feed topic (32 bytes)
|
|
1609
|
+
* @param options.owner - Optional feed owner address
|
|
1610
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1611
|
+
* @returns FeedReader with `getOwner`, `downloadReference`, and `downloadPayload`
|
|
1612
|
+
* @throws {Error} If the client is not initialized
|
|
1613
|
+
* @throws {Error} If the request times out
|
|
1614
|
+
*/
|
|
1615
|
+
makeEpochFeedReader(
|
|
1616
|
+
options: FeedReaderOptions,
|
|
1617
|
+
requestOptions?: RequestOptions,
|
|
1618
|
+
): FeedReader {
|
|
1619
|
+
this.ensureReady()
|
|
1620
|
+
const topic = this.normalizeFeedTopic(options.topic)
|
|
1621
|
+
let owner: string | undefined = options.owner
|
|
1622
|
+
? new EthAddress(options.owner).toHex()
|
|
1623
|
+
: undefined
|
|
1624
|
+
|
|
1625
|
+
const resolveOwner = async (): Promise<string> => {
|
|
1626
|
+
if (owner) {
|
|
1627
|
+
return owner
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const requestId = this.generateRequestId()
|
|
1631
|
+
const response = await this.sendRequest<
|
|
1632
|
+
FeedGetOwnerResponseMessage,
|
|
1633
|
+
FeedGetOwnerMessage
|
|
1634
|
+
>({
|
|
1635
|
+
type: "feedGetOwner",
|
|
1636
|
+
requestId,
|
|
1637
|
+
})
|
|
1638
|
+
|
|
1639
|
+
owner = response.owner
|
|
1640
|
+
return owner
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
const downloadReference = async (
|
|
1644
|
+
options?: EpochFeedDownloadOptions,
|
|
1645
|
+
): Promise<EpochFeedDownloadReferenceResult> => {
|
|
1646
|
+
const atValue =
|
|
1647
|
+
options?.at !== undefined
|
|
1648
|
+
? options.at
|
|
1649
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
1650
|
+
const requestId = this.generateRequestId()
|
|
1651
|
+
const response = await this.sendRequest<
|
|
1652
|
+
EpochFeedDownloadReferenceResponseMessage,
|
|
1653
|
+
EpochFeedDownloadReferenceMessage
|
|
1654
|
+
>({
|
|
1655
|
+
type: "epochFeedDownloadReference",
|
|
1656
|
+
requestId,
|
|
1657
|
+
topic,
|
|
1658
|
+
owner,
|
|
1659
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
1660
|
+
after:
|
|
1661
|
+
options?.after !== undefined
|
|
1662
|
+
? this.normalizeFeedTimestamp(options.after)
|
|
1663
|
+
: undefined,
|
|
1664
|
+
encryptionKey:
|
|
1665
|
+
options?.encryptionKey !== undefined
|
|
1666
|
+
? this.normalizeSocKey(options.encryptionKey)
|
|
1667
|
+
: undefined,
|
|
1668
|
+
requestOptions,
|
|
1669
|
+
})
|
|
1670
|
+
const reference = response.reference
|
|
1671
|
+
const cleanRef =
|
|
1672
|
+
reference && reference.startsWith("0x") ? reference.slice(2) : reference
|
|
1673
|
+
const encryptionKey =
|
|
1674
|
+
cleanRef && cleanRef.length === 128 ? cleanRef.slice(64) : undefined
|
|
1675
|
+
return { reference, encryptionKey }
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const downloadPayload = async (
|
|
1679
|
+
options?: EpochFeedDownloadOptions,
|
|
1680
|
+
): Promise<EpochFeedDownloadPayloadResult> => {
|
|
1681
|
+
const result = await downloadReference(options)
|
|
1682
|
+
if (!result.reference) {
|
|
1683
|
+
return {
|
|
1684
|
+
reference: undefined,
|
|
1685
|
+
payload: undefined,
|
|
1686
|
+
encryptionKey: undefined,
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
const payload = await this.downloadData(
|
|
1690
|
+
result.reference,
|
|
1691
|
+
undefined,
|
|
1692
|
+
requestOptions,
|
|
1693
|
+
)
|
|
1694
|
+
return {
|
|
1695
|
+
reference: result.reference,
|
|
1696
|
+
payload,
|
|
1697
|
+
encryptionKey: result.encryptionKey,
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
const downloadRawReference = async (
|
|
1702
|
+
options?: Omit<EpochFeedDownloadOptions, "encryptionKey">,
|
|
1703
|
+
): Promise<EpochFeedDownloadReferenceResult> => {
|
|
1704
|
+
const atValue =
|
|
1705
|
+
options?.at !== undefined
|
|
1706
|
+
? options.at
|
|
1707
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
1708
|
+
const requestId = this.generateRequestId()
|
|
1709
|
+
const response = await this.sendRequest<
|
|
1710
|
+
EpochFeedDownloadReferenceResponseMessage,
|
|
1711
|
+
EpochFeedDownloadReferenceMessage
|
|
1712
|
+
>({
|
|
1713
|
+
type: "epochFeedDownloadReference",
|
|
1714
|
+
requestId,
|
|
1715
|
+
topic,
|
|
1716
|
+
owner,
|
|
1717
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
1718
|
+
after:
|
|
1719
|
+
options?.after !== undefined
|
|
1720
|
+
? this.normalizeFeedTimestamp(options.after)
|
|
1721
|
+
: undefined,
|
|
1722
|
+
encryptionKey: undefined, // No encryption for raw download
|
|
1723
|
+
requestOptions,
|
|
1724
|
+
})
|
|
1725
|
+
const reference = response.reference
|
|
1726
|
+
return { reference, encryptionKey: undefined }
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
const downloadRawPayload = async (
|
|
1730
|
+
options?: Omit<EpochFeedDownloadOptions, "encryptionKey">,
|
|
1731
|
+
): Promise<EpochFeedDownloadPayloadResult> => {
|
|
1732
|
+
const result = await downloadRawReference(options)
|
|
1733
|
+
if (!result.reference) {
|
|
1734
|
+
return {
|
|
1735
|
+
reference: undefined,
|
|
1736
|
+
payload: undefined,
|
|
1737
|
+
encryptionKey: undefined,
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const payload = await this.downloadData(
|
|
1741
|
+
result.reference,
|
|
1742
|
+
undefined,
|
|
1743
|
+
requestOptions,
|
|
1744
|
+
)
|
|
1745
|
+
return {
|
|
1746
|
+
reference: result.reference,
|
|
1747
|
+
payload,
|
|
1748
|
+
encryptionKey: undefined,
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
return {
|
|
1753
|
+
getOwner: resolveOwner,
|
|
1754
|
+
downloadReference,
|
|
1755
|
+
downloadPayload,
|
|
1756
|
+
downloadRawReference,
|
|
1757
|
+
downloadRawPayload,
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
/**
|
|
1762
|
+
* Returns an object for reading and writing epoch-based feeds.
|
|
1763
|
+
*
|
|
1764
|
+
* @param options - Feed writer options
|
|
1765
|
+
* @param options.topic - Feed topic (32 bytes)
|
|
1766
|
+
* @param options.signer - Optional feed signer private key. If omitted, the proxy uses the app signer.
|
|
1767
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
1768
|
+
* @returns FeedWriter with `getOwner`, `downloadReference`, `downloadPayload`, `uploadPayload`, and `uploadReference`
|
|
1769
|
+
* @throws {Error} If the client is not initialized
|
|
1770
|
+
* @throws {Error} If the request times out
|
|
1771
|
+
*/
|
|
1772
|
+
makeEpochFeedWriter(
|
|
1773
|
+
options: FeedWriterOptions,
|
|
1774
|
+
requestOptions?: RequestOptions,
|
|
1775
|
+
): FeedWriter {
|
|
1776
|
+
this.ensureReady()
|
|
1777
|
+
const topic = this.normalizeFeedTopic(options.topic)
|
|
1778
|
+
const signerObj = options.signer
|
|
1779
|
+
? new PrivateKey(options.signer)
|
|
1780
|
+
: undefined
|
|
1781
|
+
const signerKey = signerObj ? signerObj.toHex() : undefined
|
|
1782
|
+
let owner: string | undefined = signerObj
|
|
1783
|
+
? signerObj.publicKey().address().toHex()
|
|
1784
|
+
: undefined
|
|
1785
|
+
|
|
1786
|
+
const resolveOwner = async (): Promise<string> => {
|
|
1787
|
+
if (owner) {
|
|
1788
|
+
return owner
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
const requestId = this.generateRequestId()
|
|
1792
|
+
const response = await this.sendRequest<
|
|
1793
|
+
FeedGetOwnerResponseMessage,
|
|
1794
|
+
FeedGetOwnerMessage
|
|
1795
|
+
>({
|
|
1796
|
+
type: "feedGetOwner",
|
|
1797
|
+
requestId,
|
|
1798
|
+
})
|
|
1799
|
+
|
|
1800
|
+
owner = response.owner
|
|
1801
|
+
return owner
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
const downloadReference = async (
|
|
1805
|
+
options?: EpochFeedDownloadOptions,
|
|
1806
|
+
): Promise<EpochFeedDownloadReferenceResult> => {
|
|
1807
|
+
const atValue =
|
|
1808
|
+
options?.at !== undefined
|
|
1809
|
+
? options.at
|
|
1810
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
1811
|
+
const requestId = this.generateRequestId()
|
|
1812
|
+
const response = await this.sendRequest<
|
|
1813
|
+
EpochFeedDownloadReferenceResponseMessage,
|
|
1814
|
+
EpochFeedDownloadReferenceMessage
|
|
1815
|
+
>({
|
|
1816
|
+
type: "epochFeedDownloadReference",
|
|
1817
|
+
requestId,
|
|
1818
|
+
topic,
|
|
1819
|
+
owner,
|
|
1820
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
1821
|
+
after:
|
|
1822
|
+
options?.after !== undefined
|
|
1823
|
+
? this.normalizeFeedTimestamp(options.after)
|
|
1824
|
+
: undefined,
|
|
1825
|
+
encryptionKey:
|
|
1826
|
+
options?.encryptionKey !== undefined
|
|
1827
|
+
? this.normalizeSocKey(options.encryptionKey)
|
|
1828
|
+
: undefined,
|
|
1829
|
+
requestOptions,
|
|
1830
|
+
})
|
|
1831
|
+
const reference = response.reference
|
|
1832
|
+
const cleanRef =
|
|
1833
|
+
reference && reference.startsWith("0x") ? reference.slice(2) : reference
|
|
1834
|
+
const encryptionKey =
|
|
1835
|
+
cleanRef && cleanRef.length === 128 ? cleanRef.slice(64) : undefined
|
|
1836
|
+
return { reference, encryptionKey }
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
const downloadPayload = async (
|
|
1840
|
+
options?: EpochFeedDownloadOptions,
|
|
1841
|
+
): Promise<EpochFeedDownloadPayloadResult> => {
|
|
1842
|
+
const result = await downloadReference(options)
|
|
1843
|
+
if (!result.reference) {
|
|
1844
|
+
return {
|
|
1845
|
+
reference: undefined,
|
|
1846
|
+
payload: undefined,
|
|
1847
|
+
encryptionKey: undefined,
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
const payload = await this.downloadData(
|
|
1851
|
+
result.reference,
|
|
1852
|
+
undefined,
|
|
1853
|
+
requestOptions,
|
|
1854
|
+
)
|
|
1855
|
+
return {
|
|
1856
|
+
reference: result.reference,
|
|
1857
|
+
payload,
|
|
1858
|
+
encryptionKey: result.encryptionKey,
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
const uploadReference = async (
|
|
1863
|
+
reference: Uint8Array | string,
|
|
1864
|
+
options?: EpochFeedUploadOptions,
|
|
1865
|
+
): Promise<EpochFeedUploadResult> => {
|
|
1866
|
+
const atValue =
|
|
1867
|
+
options?.at !== undefined
|
|
1868
|
+
? options.at
|
|
1869
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
1870
|
+
const normalizedRef = this.normalizeReference(reference)
|
|
1871
|
+
const cleanRef = normalizedRef.startsWith("0x")
|
|
1872
|
+
? normalizedRef.slice(2)
|
|
1873
|
+
: normalizedRef
|
|
1874
|
+
const derivedKey =
|
|
1875
|
+
cleanRef.length === 128 ? cleanRef.slice(64) : undefined
|
|
1876
|
+
const feedKey = options?.encryptionKey ?? derivedKey
|
|
1877
|
+
const requestId = this.generateRequestId()
|
|
1878
|
+
const response = await this.sendRequest<
|
|
1879
|
+
EpochFeedUploadReferenceResponseMessage,
|
|
1880
|
+
EpochFeedUploadReferenceMessage
|
|
1881
|
+
>({
|
|
1882
|
+
type: "epochFeedUploadReference",
|
|
1883
|
+
requestId,
|
|
1884
|
+
topic,
|
|
1885
|
+
signer: signerKey,
|
|
1886
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
1887
|
+
reference: normalizedRef,
|
|
1888
|
+
encryptionKey:
|
|
1889
|
+
feedKey !== undefined ? this.normalizeSocKey(feedKey) : undefined,
|
|
1890
|
+
hints: options?.hints,
|
|
1891
|
+
requestOptions,
|
|
1892
|
+
})
|
|
1893
|
+
const socAddress = response.socAddress
|
|
1894
|
+
const encryptionKey = derivedKey
|
|
1895
|
+
return {
|
|
1896
|
+
socAddress,
|
|
1897
|
+
reference: normalizedRef,
|
|
1898
|
+
encryptionKey,
|
|
1899
|
+
epoch: response.epoch,
|
|
1900
|
+
timestamp: response.timestamp,
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
const uploadPayload = async (
|
|
1905
|
+
data: Uint8Array | string,
|
|
1906
|
+
options?: EpochFeedUploadOptions,
|
|
1907
|
+
): Promise<EpochFeedUploadResult> => {
|
|
1908
|
+
const atValue =
|
|
1909
|
+
options?.at !== undefined
|
|
1910
|
+
? options.at
|
|
1911
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
1912
|
+
const encrypt = options?.encrypt !== false
|
|
1913
|
+
const uploadResult = await this.uploadData(
|
|
1914
|
+
this.normalizePayload(data),
|
|
1915
|
+
{ ...options?.uploadOptions, encrypt },
|
|
1916
|
+
requestOptions,
|
|
1917
|
+
)
|
|
1918
|
+
const cleanRef = uploadResult.reference.startsWith("0x")
|
|
1919
|
+
? uploadResult.reference.slice(2)
|
|
1920
|
+
: uploadResult.reference
|
|
1921
|
+
const derivedKey =
|
|
1922
|
+
cleanRef.length === 128 ? cleanRef.slice(64) : undefined
|
|
1923
|
+
const feedKey = options?.encryptionKey ?? derivedKey
|
|
1924
|
+
const requestId = this.generateRequestId()
|
|
1925
|
+
const response = await this.sendRequest<
|
|
1926
|
+
EpochFeedUploadReferenceResponseMessage,
|
|
1927
|
+
EpochFeedUploadReferenceMessage
|
|
1928
|
+
>({
|
|
1929
|
+
type: "epochFeedUploadReference",
|
|
1930
|
+
requestId,
|
|
1931
|
+
topic,
|
|
1932
|
+
signer: signerKey,
|
|
1933
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
1934
|
+
reference: uploadResult.reference,
|
|
1935
|
+
encryptionKey:
|
|
1936
|
+
feedKey !== undefined ? this.normalizeSocKey(feedKey) : undefined,
|
|
1937
|
+
hints: options?.hints,
|
|
1938
|
+
requestOptions,
|
|
1939
|
+
})
|
|
1940
|
+
const socAddress = response.socAddress
|
|
1941
|
+
const encryptionKey = derivedKey
|
|
1942
|
+
return {
|
|
1943
|
+
socAddress,
|
|
1944
|
+
reference: uploadResult.reference,
|
|
1945
|
+
encryptionKey,
|
|
1946
|
+
epoch: response.epoch,
|
|
1947
|
+
timestamp: response.timestamp,
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
const downloadRawReference = async (
|
|
1952
|
+
options?: Omit<EpochFeedDownloadOptions, "encryptionKey">,
|
|
1953
|
+
): Promise<EpochFeedDownloadReferenceResult> => {
|
|
1954
|
+
const atValue =
|
|
1955
|
+
options?.at !== undefined
|
|
1956
|
+
? options.at
|
|
1957
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
1958
|
+
const requestId = this.generateRequestId()
|
|
1959
|
+
const response = await this.sendRequest<
|
|
1960
|
+
EpochFeedDownloadReferenceResponseMessage,
|
|
1961
|
+
EpochFeedDownloadReferenceMessage
|
|
1962
|
+
>({
|
|
1963
|
+
type: "epochFeedDownloadReference",
|
|
1964
|
+
requestId,
|
|
1965
|
+
topic,
|
|
1966
|
+
owner,
|
|
1967
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
1968
|
+
after:
|
|
1969
|
+
options?.after !== undefined
|
|
1970
|
+
? this.normalizeFeedTimestamp(options.after)
|
|
1971
|
+
: undefined,
|
|
1972
|
+
encryptionKey: undefined, // No encryption for raw download
|
|
1973
|
+
requestOptions,
|
|
1974
|
+
})
|
|
1975
|
+
const reference = response.reference
|
|
1976
|
+
return { reference, encryptionKey: undefined }
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
const downloadRawPayload = async (
|
|
1980
|
+
options?: Omit<EpochFeedDownloadOptions, "encryptionKey">,
|
|
1981
|
+
): Promise<EpochFeedDownloadPayloadResult> => {
|
|
1982
|
+
const result = await downloadRawReference(options)
|
|
1983
|
+
if (!result.reference) {
|
|
1984
|
+
return {
|
|
1985
|
+
reference: undefined,
|
|
1986
|
+
payload: undefined,
|
|
1987
|
+
encryptionKey: undefined,
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
const payload = await this.downloadData(
|
|
1991
|
+
result.reference,
|
|
1992
|
+
undefined,
|
|
1993
|
+
requestOptions,
|
|
1994
|
+
)
|
|
1995
|
+
return {
|
|
1996
|
+
reference: result.reference,
|
|
1997
|
+
payload,
|
|
1998
|
+
encryptionKey: undefined,
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
const uploadRawReference = async (
|
|
2003
|
+
reference: Uint8Array | string,
|
|
2004
|
+
options?: Omit<EpochFeedUploadOptions, "encryptionKey">,
|
|
2005
|
+
): Promise<EpochFeedUploadResult> => {
|
|
2006
|
+
const atValue =
|
|
2007
|
+
options?.at !== undefined
|
|
2008
|
+
? options.at
|
|
2009
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
2010
|
+
const normalizedRef = this.normalizeReference(reference)
|
|
2011
|
+
const requestId = this.generateRequestId()
|
|
2012
|
+
const response = await this.sendRequest<
|
|
2013
|
+
EpochFeedUploadReferenceResponseMessage,
|
|
2014
|
+
EpochFeedUploadReferenceMessage
|
|
2015
|
+
>({
|
|
2016
|
+
type: "epochFeedUploadReference",
|
|
2017
|
+
requestId,
|
|
2018
|
+
topic,
|
|
2019
|
+
signer: signerKey,
|
|
2020
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
2021
|
+
reference: normalizedRef,
|
|
2022
|
+
encryptionKey: undefined, // No encryption for raw upload
|
|
2023
|
+
hints: options?.hints,
|
|
2024
|
+
requestOptions,
|
|
2025
|
+
})
|
|
2026
|
+
const socAddress = response.socAddress
|
|
2027
|
+
return {
|
|
2028
|
+
socAddress,
|
|
2029
|
+
reference: normalizedRef,
|
|
2030
|
+
encryptionKey: undefined,
|
|
2031
|
+
epoch: response.epoch,
|
|
2032
|
+
timestamp: response.timestamp,
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
const uploadRawPayload = async (
|
|
2037
|
+
data: Uint8Array | string,
|
|
2038
|
+
options?: Omit<EpochFeedUploadOptions, "encryptionKey" | "encrypt">,
|
|
2039
|
+
): Promise<EpochFeedUploadResult> => {
|
|
2040
|
+
const atValue =
|
|
2041
|
+
options?.at !== undefined
|
|
2042
|
+
? options.at
|
|
2043
|
+
: BigInt(Math.floor(Date.now() / 1000))
|
|
2044
|
+
// Upload with encrypt: false for raw payload
|
|
2045
|
+
const uploadResult = await this.uploadData(
|
|
2046
|
+
this.normalizePayload(data),
|
|
2047
|
+
{ ...options?.uploadOptions, encrypt: false },
|
|
2048
|
+
requestOptions,
|
|
2049
|
+
)
|
|
2050
|
+
const requestId = this.generateRequestId()
|
|
2051
|
+
const response = await this.sendRequest<
|
|
2052
|
+
EpochFeedUploadReferenceResponseMessage,
|
|
2053
|
+
EpochFeedUploadReferenceMessage
|
|
2054
|
+
>({
|
|
2055
|
+
type: "epochFeedUploadReference",
|
|
2056
|
+
requestId,
|
|
2057
|
+
topic,
|
|
2058
|
+
signer: signerKey,
|
|
2059
|
+
at: this.normalizeFeedTimestamp(atValue),
|
|
2060
|
+
reference: uploadResult.reference,
|
|
2061
|
+
encryptionKey: undefined, // No encryption for raw upload
|
|
2062
|
+
hints: options?.hints,
|
|
2063
|
+
requestOptions,
|
|
2064
|
+
})
|
|
2065
|
+
const socAddress = response.socAddress
|
|
2066
|
+
return {
|
|
2067
|
+
socAddress,
|
|
2068
|
+
reference: uploadResult.reference,
|
|
2069
|
+
encryptionKey: undefined,
|
|
2070
|
+
epoch: response.epoch,
|
|
2071
|
+
timestamp: response.timestamp,
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
return {
|
|
2076
|
+
getOwner: resolveOwner,
|
|
2077
|
+
downloadReference,
|
|
2078
|
+
downloadPayload,
|
|
2079
|
+
downloadRawReference,
|
|
2080
|
+
downloadRawPayload,
|
|
2081
|
+
uploadReference,
|
|
2082
|
+
uploadPayload,
|
|
2083
|
+
uploadRawReference,
|
|
2084
|
+
uploadRawPayload,
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
/**
|
|
2089
|
+
* Returns a sequential feed reader (chunk API only).
|
|
2090
|
+
*
|
|
2091
|
+
* @param options - Sequential feed reader options
|
|
2092
|
+
* @param options.topic - Feed topic (32 bytes)
|
|
2093
|
+
* @param options.owner - Optional feed owner address
|
|
2094
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
2095
|
+
* @returns SequentialFeedReader with payload/reference download helpers
|
|
2096
|
+
*/
|
|
2097
|
+
makeSequentialFeedReader(
|
|
2098
|
+
options: SequentialFeedReaderOptions,
|
|
2099
|
+
requestOptions?: RequestOptions,
|
|
2100
|
+
): SequentialFeedReader {
|
|
2101
|
+
this.ensureReady()
|
|
2102
|
+
const topic = this.normalizeFeedTopic(options.topic)
|
|
2103
|
+
let owner: string | undefined = options.owner
|
|
2104
|
+
? new EthAddress(options.owner).toHex()
|
|
2105
|
+
: undefined
|
|
2106
|
+
|
|
2107
|
+
const resolveOwner = async (): Promise<string> => {
|
|
2108
|
+
if (owner) {
|
|
2109
|
+
return owner
|
|
2110
|
+
}
|
|
2111
|
+
const requestId = this.generateRequestId()
|
|
2112
|
+
const response = await this.sendRequest<
|
|
2113
|
+
SequentialFeedGetOwnerResponseMessage,
|
|
2114
|
+
SequentialFeedGetOwnerMessage
|
|
2115
|
+
>({
|
|
2116
|
+
type: "seqFeedGetOwner",
|
|
2117
|
+
requestId,
|
|
2118
|
+
})
|
|
2119
|
+
owner = response.owner
|
|
2120
|
+
return owner
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
const downloadPayload = async (
|
|
2124
|
+
encryptionKey: Uint8Array | string,
|
|
2125
|
+
options?: SequentialFeedUpdateOptions,
|
|
2126
|
+
): Promise<SequentialFeedPayloadResult> => {
|
|
2127
|
+
const requestId = this.generateRequestId()
|
|
2128
|
+
const response = await this.sendRequest<
|
|
2129
|
+
SequentialFeedDownloadPayloadResponseMessage,
|
|
2130
|
+
SequentialFeedDownloadPayloadMessage
|
|
2131
|
+
>({
|
|
2132
|
+
type: "seqFeedDownloadPayload",
|
|
2133
|
+
requestId,
|
|
2134
|
+
topic,
|
|
2135
|
+
owner,
|
|
2136
|
+
index:
|
|
2137
|
+
options?.index !== undefined
|
|
2138
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2139
|
+
: undefined,
|
|
2140
|
+
at:
|
|
2141
|
+
options?.at !== undefined
|
|
2142
|
+
? this.normalizeFeedTimestamp(
|
|
2143
|
+
options.at as bigint | number | string,
|
|
2144
|
+
)
|
|
2145
|
+
: undefined,
|
|
2146
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2147
|
+
lookupTimeoutMs: options?.lookupTimeoutMs,
|
|
2148
|
+
encryptionKey: this.normalizeSocKey(encryptionKey),
|
|
2149
|
+
requestOptions,
|
|
2150
|
+
})
|
|
2151
|
+
|
|
2152
|
+
return {
|
|
2153
|
+
payload: response.payload,
|
|
2154
|
+
timestamp: response.timestamp,
|
|
2155
|
+
feedIndex: response.feedIndex,
|
|
2156
|
+
feedIndexNext: response.feedIndexNext,
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
const downloadRawPayload = async (
|
|
2161
|
+
options?: SequentialFeedDownloadRawOptions,
|
|
2162
|
+
): Promise<SequentialFeedPayloadResult> => {
|
|
2163
|
+
const requestId = this.generateRequestId()
|
|
2164
|
+
const response = await this.sendRequest<
|
|
2165
|
+
SequentialFeedDownloadRawPayloadResponseMessage,
|
|
2166
|
+
SequentialFeedDownloadRawPayloadMessage
|
|
2167
|
+
>({
|
|
2168
|
+
type: "seqFeedDownloadRawPayload",
|
|
2169
|
+
requestId,
|
|
2170
|
+
topic,
|
|
2171
|
+
owner,
|
|
2172
|
+
index:
|
|
2173
|
+
options?.index !== undefined
|
|
2174
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2175
|
+
: undefined,
|
|
2176
|
+
at:
|
|
2177
|
+
options?.at !== undefined
|
|
2178
|
+
? this.normalizeFeedTimestamp(
|
|
2179
|
+
options.at as bigint | number | string,
|
|
2180
|
+
)
|
|
2181
|
+
: undefined,
|
|
2182
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2183
|
+
lookupTimeoutMs: options?.lookupTimeoutMs,
|
|
2184
|
+
encryptionKey: options?.encryptionKey
|
|
2185
|
+
? this.normalizeSocKey(options.encryptionKey)
|
|
2186
|
+
: undefined,
|
|
2187
|
+
requestOptions,
|
|
2188
|
+
})
|
|
2189
|
+
|
|
2190
|
+
return {
|
|
2191
|
+
payload: response.payload,
|
|
2192
|
+
timestamp: response.timestamp,
|
|
2193
|
+
feedIndex: response.feedIndex,
|
|
2194
|
+
feedIndexNext: response.feedIndexNext,
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
const downloadReference = async (
|
|
2199
|
+
encryptionKey: Uint8Array | string,
|
|
2200
|
+
options?: SequentialFeedUpdateOptions,
|
|
2201
|
+
): Promise<SequentialFeedReferenceResult> => {
|
|
2202
|
+
const requestId = this.generateRequestId()
|
|
2203
|
+
const response = await this.sendRequest<
|
|
2204
|
+
SequentialFeedDownloadReferenceResponseMessage,
|
|
2205
|
+
SequentialFeedDownloadReferenceMessage
|
|
2206
|
+
>({
|
|
2207
|
+
type: "seqFeedDownloadReference",
|
|
2208
|
+
requestId,
|
|
2209
|
+
topic,
|
|
2210
|
+
owner,
|
|
2211
|
+
index:
|
|
2212
|
+
options?.index !== undefined
|
|
2213
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2214
|
+
: undefined,
|
|
2215
|
+
at:
|
|
2216
|
+
options?.at !== undefined
|
|
2217
|
+
? this.normalizeFeedTimestamp(
|
|
2218
|
+
options.at as bigint | number | string,
|
|
2219
|
+
)
|
|
2220
|
+
: undefined,
|
|
2221
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2222
|
+
lookupTimeoutMs: options?.lookupTimeoutMs,
|
|
2223
|
+
encryptionKey: this.normalizeSocKey(encryptionKey),
|
|
2224
|
+
requestOptions,
|
|
2225
|
+
})
|
|
2226
|
+
|
|
2227
|
+
return {
|
|
2228
|
+
reference: response.reference,
|
|
2229
|
+
feedIndex: response.feedIndex,
|
|
2230
|
+
feedIndexNext: response.feedIndexNext,
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
return {
|
|
2235
|
+
getOwner: resolveOwner,
|
|
2236
|
+
downloadPayload,
|
|
2237
|
+
downloadRawPayload,
|
|
2238
|
+
downloadReference,
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/**
|
|
2243
|
+
* Returns a sequential feed writer (chunk API only).
|
|
2244
|
+
*
|
|
2245
|
+
* @param options - Sequential feed writer options
|
|
2246
|
+
* @param options.topic - Feed topic (32 bytes)
|
|
2247
|
+
* @param options.signer - Optional signer private key. If omitted, proxy uses app signer.
|
|
2248
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
2249
|
+
* @returns SequentialFeedWriter with payload/reference upload helpers
|
|
2250
|
+
*/
|
|
2251
|
+
makeSequentialFeedWriter(
|
|
2252
|
+
options: SequentialFeedWriterOptions,
|
|
2253
|
+
requestOptions?: RequestOptions,
|
|
2254
|
+
): SequentialFeedWriter {
|
|
2255
|
+
this.ensureReady()
|
|
2256
|
+
const topic = this.normalizeFeedTopic(options.topic)
|
|
2257
|
+
const signerObj = options.signer
|
|
2258
|
+
? new PrivateKey(options.signer)
|
|
2259
|
+
: undefined
|
|
2260
|
+
const signerKey = signerObj ? signerObj.toHex() : undefined
|
|
2261
|
+
let owner: string | undefined = signerObj
|
|
2262
|
+
? signerObj.publicKey().address().toHex()
|
|
2263
|
+
: undefined
|
|
2264
|
+
|
|
2265
|
+
const resolveOwner = async (): Promise<string> => {
|
|
2266
|
+
if (owner) {
|
|
2267
|
+
return owner
|
|
2268
|
+
}
|
|
2269
|
+
const requestId = this.generateRequestId()
|
|
2270
|
+
const response = await this.sendRequest<
|
|
2271
|
+
SequentialFeedGetOwnerResponseMessage,
|
|
2272
|
+
SequentialFeedGetOwnerMessage
|
|
2273
|
+
>({
|
|
2274
|
+
type: "seqFeedGetOwner",
|
|
2275
|
+
requestId,
|
|
2276
|
+
})
|
|
2277
|
+
owner = response.owner
|
|
2278
|
+
return owner
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
const downloadPayload = async (
|
|
2282
|
+
encryptionKey: Uint8Array | string,
|
|
2283
|
+
options?: SequentialFeedUpdateOptions,
|
|
2284
|
+
): Promise<SequentialFeedPayloadResult> => {
|
|
2285
|
+
const requestId = this.generateRequestId()
|
|
2286
|
+
const response = await this.sendRequest<
|
|
2287
|
+
SequentialFeedDownloadPayloadResponseMessage,
|
|
2288
|
+
SequentialFeedDownloadPayloadMessage
|
|
2289
|
+
>({
|
|
2290
|
+
type: "seqFeedDownloadPayload",
|
|
2291
|
+
requestId,
|
|
2292
|
+
topic,
|
|
2293
|
+
owner,
|
|
2294
|
+
index:
|
|
2295
|
+
options?.index !== undefined
|
|
2296
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2297
|
+
: undefined,
|
|
2298
|
+
at:
|
|
2299
|
+
options?.at !== undefined
|
|
2300
|
+
? this.normalizeFeedTimestamp(
|
|
2301
|
+
options.at as bigint | number | string,
|
|
2302
|
+
)
|
|
2303
|
+
: undefined,
|
|
2304
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2305
|
+
encryptionKey: this.normalizeSocKey(encryptionKey),
|
|
2306
|
+
requestOptions,
|
|
2307
|
+
})
|
|
2308
|
+
|
|
2309
|
+
return {
|
|
2310
|
+
payload: response.payload,
|
|
2311
|
+
timestamp: response.timestamp,
|
|
2312
|
+
feedIndex: response.feedIndex,
|
|
2313
|
+
feedIndexNext: response.feedIndexNext,
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
const downloadRawPayload = async (
|
|
2318
|
+
options?: SequentialFeedDownloadRawOptions,
|
|
2319
|
+
): Promise<SequentialFeedPayloadResult> => {
|
|
2320
|
+
const requestId = this.generateRequestId()
|
|
2321
|
+
const response = await this.sendRequest<
|
|
2322
|
+
SequentialFeedDownloadRawPayloadResponseMessage,
|
|
2323
|
+
SequentialFeedDownloadRawPayloadMessage
|
|
2324
|
+
>({
|
|
2325
|
+
type: "seqFeedDownloadRawPayload",
|
|
2326
|
+
requestId,
|
|
2327
|
+
topic,
|
|
2328
|
+
owner,
|
|
2329
|
+
index:
|
|
2330
|
+
options?.index !== undefined
|
|
2331
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2332
|
+
: undefined,
|
|
2333
|
+
at:
|
|
2334
|
+
options?.at !== undefined
|
|
2335
|
+
? this.normalizeFeedTimestamp(
|
|
2336
|
+
options.at as bigint | number | string,
|
|
2337
|
+
)
|
|
2338
|
+
: undefined,
|
|
2339
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2340
|
+
encryptionKey: options?.encryptionKey
|
|
2341
|
+
? this.normalizeSocKey(options.encryptionKey)
|
|
2342
|
+
: undefined,
|
|
2343
|
+
requestOptions,
|
|
2344
|
+
})
|
|
2345
|
+
|
|
2346
|
+
return {
|
|
2347
|
+
payload: response.payload,
|
|
2348
|
+
timestamp: response.timestamp,
|
|
2349
|
+
feedIndex: response.feedIndex,
|
|
2350
|
+
feedIndexNext: response.feedIndexNext,
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
const downloadReference = async (
|
|
2355
|
+
encryptionKey: Uint8Array | string,
|
|
2356
|
+
options?: SequentialFeedUpdateOptions,
|
|
2357
|
+
): Promise<SequentialFeedReferenceResult> => {
|
|
2358
|
+
const requestId = this.generateRequestId()
|
|
2359
|
+
const response = await this.sendRequest<
|
|
2360
|
+
SequentialFeedDownloadReferenceResponseMessage,
|
|
2361
|
+
SequentialFeedDownloadReferenceMessage
|
|
2362
|
+
>({
|
|
2363
|
+
type: "seqFeedDownloadReference",
|
|
2364
|
+
requestId,
|
|
2365
|
+
topic,
|
|
2366
|
+
owner,
|
|
2367
|
+
index:
|
|
2368
|
+
options?.index !== undefined
|
|
2369
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2370
|
+
: undefined,
|
|
2371
|
+
at:
|
|
2372
|
+
options?.at !== undefined
|
|
2373
|
+
? this.normalizeFeedTimestamp(
|
|
2374
|
+
options.at as bigint | number | string,
|
|
2375
|
+
)
|
|
2376
|
+
: undefined,
|
|
2377
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2378
|
+
encryptionKey: this.normalizeSocKey(encryptionKey),
|
|
2379
|
+
requestOptions,
|
|
2380
|
+
})
|
|
2381
|
+
|
|
2382
|
+
return {
|
|
2383
|
+
reference: response.reference,
|
|
2384
|
+
feedIndex: response.feedIndex,
|
|
2385
|
+
feedIndexNext: response.feedIndexNext,
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
const uploadPayload = async (
|
|
2390
|
+
data: Uint8Array | string,
|
|
2391
|
+
options?: SequentialFeedUploadOptions,
|
|
2392
|
+
): Promise<SequentialFeedUploadResult> => {
|
|
2393
|
+
const requestId = this.generateRequestId()
|
|
2394
|
+
const response = await this.sendRequest<
|
|
2395
|
+
SequentialFeedUploadPayloadResponseMessage,
|
|
2396
|
+
SequentialFeedUploadPayloadMessage
|
|
2397
|
+
>({
|
|
2398
|
+
type: "seqFeedUploadPayload",
|
|
2399
|
+
requestId,
|
|
2400
|
+
topic,
|
|
2401
|
+
signer: signerKey,
|
|
2402
|
+
data: this.normalizePayload(data),
|
|
2403
|
+
index:
|
|
2404
|
+
options?.index !== undefined
|
|
2405
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2406
|
+
: undefined,
|
|
2407
|
+
at:
|
|
2408
|
+
options?.at !== undefined
|
|
2409
|
+
? this.normalizeFeedTimestamp(
|
|
2410
|
+
options.at as bigint | number | string,
|
|
2411
|
+
)
|
|
2412
|
+
: undefined,
|
|
2413
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2414
|
+
lookupTimeoutMs: options?.lookupTimeoutMs,
|
|
2415
|
+
options,
|
|
2416
|
+
requestOptions,
|
|
2417
|
+
})
|
|
2418
|
+
|
|
2419
|
+
owner = response.owner
|
|
2420
|
+
|
|
2421
|
+
return {
|
|
2422
|
+
reference: response.reference,
|
|
2423
|
+
feedIndex: response.feedIndex,
|
|
2424
|
+
owner: response.owner,
|
|
2425
|
+
encryptionKey: response.encryptionKey,
|
|
2426
|
+
tagUid: response.tagUid,
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
const uploadRawPayload = async (
|
|
2431
|
+
data: Uint8Array | string,
|
|
2432
|
+
options?: SequentialFeedUploadRawOptions,
|
|
2433
|
+
): Promise<SequentialFeedUploadResult> => {
|
|
2434
|
+
const requestId = this.generateRequestId()
|
|
2435
|
+
const response = await this.sendRequest<
|
|
2436
|
+
SequentialFeedUploadRawPayloadResponseMessage,
|
|
2437
|
+
SequentialFeedUploadRawPayloadMessage
|
|
2438
|
+
>({
|
|
2439
|
+
type: "seqFeedUploadRawPayload",
|
|
2440
|
+
requestId,
|
|
2441
|
+
topic,
|
|
2442
|
+
signer: signerKey,
|
|
2443
|
+
data: this.normalizePayload(data),
|
|
2444
|
+
index:
|
|
2445
|
+
options?.index !== undefined
|
|
2446
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2447
|
+
: undefined,
|
|
2448
|
+
at:
|
|
2449
|
+
options?.at !== undefined
|
|
2450
|
+
? this.normalizeFeedTimestamp(
|
|
2451
|
+
options.at as bigint | number | string,
|
|
2452
|
+
)
|
|
2453
|
+
: undefined,
|
|
2454
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2455
|
+
lookupTimeoutMs: options?.lookupTimeoutMs,
|
|
2456
|
+
encryptionKey: options?.encryptionKey
|
|
2457
|
+
? this.normalizeSocKey(options.encryptionKey)
|
|
2458
|
+
: undefined,
|
|
2459
|
+
options,
|
|
2460
|
+
requestOptions,
|
|
2461
|
+
})
|
|
2462
|
+
|
|
2463
|
+
owner = response.owner
|
|
2464
|
+
|
|
2465
|
+
return {
|
|
2466
|
+
reference: response.reference,
|
|
2467
|
+
feedIndex: response.feedIndex,
|
|
2468
|
+
owner: response.owner,
|
|
2469
|
+
encryptionKey: response.encryptionKey,
|
|
2470
|
+
tagUid: response.tagUid,
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
const uploadReference = async (
|
|
2475
|
+
reference: Uint8Array | string,
|
|
2476
|
+
options?: SequentialFeedUploadOptions,
|
|
2477
|
+
): Promise<SequentialFeedUploadResult> => {
|
|
2478
|
+
const requestId = this.generateRequestId()
|
|
2479
|
+
const response = await this.sendRequest<
|
|
2480
|
+
SequentialFeedUploadReferenceResponseMessage,
|
|
2481
|
+
SequentialFeedUploadReferenceMessage
|
|
2482
|
+
>({
|
|
2483
|
+
type: "seqFeedUploadReference",
|
|
2484
|
+
requestId,
|
|
2485
|
+
topic,
|
|
2486
|
+
signer: signerKey,
|
|
2487
|
+
reference: this.normalizeReference(reference),
|
|
2488
|
+
index:
|
|
2489
|
+
options?.index !== undefined
|
|
2490
|
+
? this.normalizeFeedIndex(options.index as bigint | number | string)
|
|
2491
|
+
: undefined,
|
|
2492
|
+
at:
|
|
2493
|
+
options?.at !== undefined
|
|
2494
|
+
? this.normalizeFeedTimestamp(
|
|
2495
|
+
options.at as bigint | number | string,
|
|
2496
|
+
)
|
|
2497
|
+
: undefined,
|
|
2498
|
+
hasTimestamp: options?.hasTimestamp,
|
|
2499
|
+
lookupTimeoutMs: options?.lookupTimeoutMs,
|
|
2500
|
+
options,
|
|
2501
|
+
requestOptions,
|
|
2502
|
+
})
|
|
2503
|
+
|
|
2504
|
+
owner = response.owner
|
|
2505
|
+
|
|
2506
|
+
return {
|
|
2507
|
+
reference: response.reference,
|
|
2508
|
+
feedIndex: response.feedIndex,
|
|
2509
|
+
owner: response.owner,
|
|
2510
|
+
encryptionKey: response.encryptionKey,
|
|
2511
|
+
tagUid: response.tagUid,
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
return {
|
|
2516
|
+
getOwner: resolveOwner,
|
|
2517
|
+
downloadPayload,
|
|
2518
|
+
downloadRawPayload,
|
|
2519
|
+
downloadReference,
|
|
2520
|
+
uploadPayload,
|
|
2521
|
+
uploadRawPayload,
|
|
2522
|
+
uploadReference,
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
// ============================================================================
|
|
2527
|
+
// Feed Manifest Methods
|
|
2528
|
+
// ============================================================================
|
|
2529
|
+
|
|
2530
|
+
/**
|
|
2531
|
+
* Creates a feed manifest for accessing feed content via URL.
|
|
2532
|
+
*
|
|
2533
|
+
* A feed manifest enables accessing the latest feed content via a URL path
|
|
2534
|
+
* (e.g., `/bzz/{manifest-reference}/`). The manifest stores metadata about
|
|
2535
|
+
* the feed including owner, topic, and type.
|
|
2536
|
+
*
|
|
2537
|
+
* @param topic - Feed topic (32-byte hex string)
|
|
2538
|
+
* @param options - Optional configuration
|
|
2539
|
+
* @param options.owner - Feed owner address; if omitted, uses app signer
|
|
2540
|
+
* @param options.uploadOptions - Upload configuration (pin, deferred, etc.)
|
|
2541
|
+
* @param requestOptions - Request configuration (timeout, headers)
|
|
2542
|
+
* @returns Promise resolving to the manifest reference
|
|
2543
|
+
* @throws {Error} If the client is not initialized
|
|
2544
|
+
* @throws {Error} If no owner is provided and no app signer is available
|
|
2545
|
+
* @throws {Error} If the request times out
|
|
2546
|
+
*
|
|
2547
|
+
* @example
|
|
2548
|
+
* ```typescript
|
|
2549
|
+
* // Create manifest for a feed (uses app signer as owner)
|
|
2550
|
+
* const manifestRef = await client.createFeedManifest(topic)
|
|
2551
|
+
* console.log('Feed accessible at /bzz/' + manifestRef)
|
|
2552
|
+
*
|
|
2553
|
+
* // Create manifest with explicit owner
|
|
2554
|
+
* const manifestRef = await client.createFeedManifest(topic, {
|
|
2555
|
+
* owner: '0x1234...',
|
|
2556
|
+
* uploadOptions: { pin: true }
|
|
2557
|
+
* })
|
|
2558
|
+
* ```
|
|
2559
|
+
*/
|
|
2560
|
+
async createFeedManifest(
|
|
2561
|
+
topic: string,
|
|
2562
|
+
options?: {
|
|
2563
|
+
owner?: string
|
|
2564
|
+
/** Feed type: "Sequence" for sequential feeds, "Epoch" for epoch feeds. Default: "Sequence" */
|
|
2565
|
+
feedType?: "Sequence" | "Epoch"
|
|
2566
|
+
uploadOptions?: UploadOptions
|
|
2567
|
+
},
|
|
2568
|
+
requestOptions?: RequestOptions,
|
|
2569
|
+
): Promise<string> {
|
|
2570
|
+
this.ensureReady()
|
|
2571
|
+
const normalizedTopic = this.normalizeFeedTopic(topic)
|
|
2572
|
+
const requestId = this.generateRequestId()
|
|
2573
|
+
|
|
2574
|
+
const response = await this.sendRequest<
|
|
2575
|
+
CreateFeedManifestResponseMessage,
|
|
2576
|
+
CreateFeedManifestMessage
|
|
2577
|
+
>({
|
|
2578
|
+
type: "createFeedManifest",
|
|
2579
|
+
requestId,
|
|
2580
|
+
topic: normalizedTopic,
|
|
2581
|
+
owner: options?.owner,
|
|
2582
|
+
feedType: options?.feedType,
|
|
2583
|
+
uploadOptions: options?.uploadOptions,
|
|
2584
|
+
requestOptions,
|
|
2585
|
+
})
|
|
2586
|
+
|
|
2587
|
+
return response.reference
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// ============================================================================
|
|
2591
|
+
// GSOC Methods
|
|
2592
|
+
// ============================================================================
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* Mines a private key whose SOC address is proximate to a target overlay.
|
|
2596
|
+
*
|
|
2597
|
+
* This is a synchronous, pure computation that does not require authentication.
|
|
2598
|
+
* The mined signer can be used with {@link gsocSend} to send GSOC messages
|
|
2599
|
+
* that route to the target overlay node.
|
|
2600
|
+
*
|
|
2601
|
+
* @param targetOverlay - The target overlay address to mine proximity for
|
|
2602
|
+
* @param identifier - The GSOC identifier
|
|
2603
|
+
* @param proximity - Optional proximity depth (defaults to 12 in bee-js)
|
|
2604
|
+
* @returns A promise resolving to the mined signer as a hex string (private key)
|
|
2605
|
+
* @throws {Error} If the client is not initialized
|
|
2606
|
+
* @throws {Error} If no valid signer can be mined
|
|
2607
|
+
* @throws {Error} If the request times out
|
|
2608
|
+
*
|
|
2609
|
+
* @example
|
|
2610
|
+
* ```typescript
|
|
2611
|
+
* const signer = await client.gsocMine(targetOverlay, identifier)
|
|
2612
|
+
* // Use signer with gsocSend
|
|
2613
|
+
* await client.gsocSend(signer, identifier, data)
|
|
2614
|
+
* ```
|
|
2615
|
+
*/
|
|
2616
|
+
async gsocMine(
|
|
2617
|
+
targetOverlay: string,
|
|
2618
|
+
identifier: string,
|
|
2619
|
+
proximity?: number,
|
|
2620
|
+
): Promise<string> {
|
|
2621
|
+
this.ensureReady()
|
|
2622
|
+
const requestId = this.generateRequestId()
|
|
2623
|
+
|
|
2624
|
+
const response = await this.sendRequest<{
|
|
2625
|
+
type: "gsocMineResponse"
|
|
2626
|
+
requestId: string
|
|
2627
|
+
signer: string
|
|
2628
|
+
}>({
|
|
2629
|
+
type: "gsocMine",
|
|
2630
|
+
requestId,
|
|
2631
|
+
targetOverlay,
|
|
2632
|
+
identifier,
|
|
2633
|
+
proximity,
|
|
2634
|
+
})
|
|
2635
|
+
|
|
2636
|
+
return response.signer
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
/**
|
|
2640
|
+
* Sends a GSOC (Global Single Owner Chunk) message using a mined signer.
|
|
2641
|
+
*
|
|
2642
|
+
* The signer should be obtained from {@link gsocMine}. The message is sent
|
|
2643
|
+
* using the proxy's stored postage batch ID.
|
|
2644
|
+
*
|
|
2645
|
+
* @param signer - The mined signer as a hex string (from gsocMine)
|
|
2646
|
+
* @param identifier - The GSOC identifier
|
|
2647
|
+
* @param data - The message data to send
|
|
2648
|
+
* @param options - Optional upload configuration
|
|
2649
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
2650
|
+
* @returns A promise resolving to the upload result with reference and optional tagUid
|
|
2651
|
+
* @throws {Error} If the client is not initialized
|
|
2652
|
+
* @throws {Error} If the user is not authenticated
|
|
2653
|
+
* @throws {Error} If no postage batch ID is available
|
|
2654
|
+
* @throws {Error} If the request times out
|
|
2655
|
+
*
|
|
2656
|
+
* @example
|
|
2657
|
+
* ```typescript
|
|
2658
|
+
* const signer = await client.gsocMine(targetOverlay, identifier)
|
|
2659
|
+
* const result = await client.gsocSend(signer, identifier, new TextEncoder().encode('Hello!'))
|
|
2660
|
+
* console.log('GSOC reference:', result.reference)
|
|
2661
|
+
* ```
|
|
2662
|
+
*/
|
|
2663
|
+
async gsocSend(
|
|
2664
|
+
signer: string,
|
|
2665
|
+
identifier: string,
|
|
2666
|
+
data: Uint8Array,
|
|
2667
|
+
options?: UploadOptions,
|
|
2668
|
+
requestOptions?: RequestOptions,
|
|
2669
|
+
): Promise<UploadResult> {
|
|
2670
|
+
this.ensureReady()
|
|
2671
|
+
const requestId = this.generateRequestId()
|
|
2672
|
+
|
|
2673
|
+
const response = await this.sendRequest<{
|
|
2674
|
+
type: "gsocSendResponse"
|
|
2675
|
+
requestId: string
|
|
2676
|
+
reference: Reference
|
|
2677
|
+
tagUid?: number
|
|
2678
|
+
}>({
|
|
2679
|
+
type: "gsocSend",
|
|
2680
|
+
requestId,
|
|
2681
|
+
signer,
|
|
2682
|
+
identifier,
|
|
2683
|
+
data: new Uint8Array(data),
|
|
2684
|
+
options,
|
|
2685
|
+
requestOptions,
|
|
2686
|
+
})
|
|
2687
|
+
|
|
2688
|
+
return {
|
|
2689
|
+
reference: response.reference,
|
|
2690
|
+
tagUid: response.tagUid,
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
// ============================================================================
|
|
2695
|
+
// ACT (Access Control Tries) Methods
|
|
2696
|
+
// ============================================================================
|
|
2697
|
+
|
|
2698
|
+
/**
|
|
2699
|
+
* Uploads data with ACT (Access Control Tries) protection.
|
|
2700
|
+
*
|
|
2701
|
+
* This method encrypts the data and creates an ACT that controls who can decrypt it.
|
|
2702
|
+
* Only the specified grantees (and the publisher) can decrypt and access the data.
|
|
2703
|
+
*
|
|
2704
|
+
* @param data - The binary data to upload as a Uint8Array
|
|
2705
|
+
* @param grantees - Array of grantee public keys as compressed hex strings (33 bytes = 66 hex chars)
|
|
2706
|
+
* @param options - Optional upload configuration
|
|
2707
|
+
* @param options.pin - Whether to pin the data locally (defaults to false)
|
|
2708
|
+
* @param options.tag - Tag ID for tracking upload progress
|
|
2709
|
+
* @param options.deferred - Whether to use deferred upload (defaults to false)
|
|
2710
|
+
* @param options.redundancyLevel - Redundancy level from 0-4 for data availability
|
|
2711
|
+
* @param options.onProgress - Optional callback for tracking upload progress
|
|
2712
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
2713
|
+
* @returns A promise resolving to the ACT upload result
|
|
2714
|
+
* @returns return.encryptedReference - The encrypted reference that must be stored with the ACT
|
|
2715
|
+
* @returns return.actReference - The Swarm reference (hash) of the ACT manifest
|
|
2716
|
+
* @returns return.historyReference - The Swarm reference of the history manifest (use for future operations)
|
|
2717
|
+
* @returns return.granteeListReference - The Swarm reference of the encrypted grantee list
|
|
2718
|
+
* @returns return.publisherPubKey - The publisher's compressed public key (share with grantees)
|
|
2719
|
+
* @returns return.tagUid - The tag UID if a tag was created
|
|
2720
|
+
* @throws {Error} If the client is not initialized
|
|
2721
|
+
* @throws {Error} If the user is not authenticated or cannot upload
|
|
2722
|
+
* @throws {Error} If the request times out
|
|
2723
|
+
*
|
|
2724
|
+
* @example
|
|
2725
|
+
* ```typescript
|
|
2726
|
+
* const data = new TextEncoder().encode('Secret message')
|
|
2727
|
+
* const grantees = ['03a1b2c3...'] // Compressed public keys of allowed readers
|
|
2728
|
+
* const result = await client.actUploadData(data, grantees, {
|
|
2729
|
+
* onProgress: (progress) => {
|
|
2730
|
+
* console.log(`Progress: ${progress.processed}/${progress.total}`)
|
|
2731
|
+
* },
|
|
2732
|
+
* })
|
|
2733
|
+
* console.log('History Reference:', result.historyReference)
|
|
2734
|
+
* console.log('Encrypted Reference:', result.encryptedReference)
|
|
2735
|
+
* console.log('Publisher Public Key:', result.publisherPubKey)
|
|
2736
|
+
* ```
|
|
2737
|
+
*/
|
|
2738
|
+
async actUploadData(
|
|
2739
|
+
data: Uint8Array,
|
|
2740
|
+
grantees: string[],
|
|
2741
|
+
options?: ActUploadOptions,
|
|
2742
|
+
requestOptions?: RequestOptions,
|
|
2743
|
+
): Promise<{
|
|
2744
|
+
encryptedReference: string
|
|
2745
|
+
historyReference: string
|
|
2746
|
+
granteeListReference: string
|
|
2747
|
+
publisherPubKey: string
|
|
2748
|
+
actReference: string
|
|
2749
|
+
tagUid?: number
|
|
2750
|
+
}> {
|
|
2751
|
+
this.ensureReady()
|
|
2752
|
+
const requestId = this.generateRequestId()
|
|
2753
|
+
const { onProgress, ...serializableOptions } = options ?? {}
|
|
2754
|
+
|
|
2755
|
+
// Setup progress listener if callback provided
|
|
2756
|
+
let progressListener: ((event: MessageEvent) => void) | undefined
|
|
2757
|
+
if (onProgress) {
|
|
2758
|
+
progressListener = (event: MessageEvent) => {
|
|
2759
|
+
if (event.origin !== new URL(this.iframeOrigin).origin) return
|
|
2760
|
+
|
|
2761
|
+
try {
|
|
2762
|
+
const message = IframeToParentMessageSchema.parse(event.data)
|
|
2763
|
+
if (
|
|
2764
|
+
message.type === "uploadProgress" &&
|
|
2765
|
+
message.requestId === requestId
|
|
2766
|
+
) {
|
|
2767
|
+
onProgress({
|
|
2768
|
+
total: message.total,
|
|
2769
|
+
processed: message.processed,
|
|
2770
|
+
})
|
|
2771
|
+
}
|
|
2772
|
+
} catch {
|
|
2773
|
+
// Ignore invalid messages
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
window.addEventListener("message", progressListener)
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
try {
|
|
2780
|
+
const response = await this.sendRequest<{
|
|
2781
|
+
type: "actUploadDataResponse"
|
|
2782
|
+
requestId: string
|
|
2783
|
+
encryptedReference: string
|
|
2784
|
+
historyReference: string
|
|
2785
|
+
granteeListReference: string
|
|
2786
|
+
publisherPubKey: string
|
|
2787
|
+
actReference: string
|
|
2788
|
+
tagUid?: number
|
|
2789
|
+
}>({
|
|
2790
|
+
type: "actUploadData",
|
|
2791
|
+
requestId,
|
|
2792
|
+
data: new Uint8Array(data),
|
|
2793
|
+
grantees,
|
|
2794
|
+
options: serializableOptions,
|
|
2795
|
+
requestOptions,
|
|
2796
|
+
enableProgress: !!onProgress,
|
|
2797
|
+
})
|
|
2798
|
+
|
|
2799
|
+
return {
|
|
2800
|
+
encryptedReference: response.encryptedReference,
|
|
2801
|
+
historyReference: response.historyReference,
|
|
2802
|
+
granteeListReference: response.granteeListReference,
|
|
2803
|
+
publisherPubKey: response.publisherPubKey,
|
|
2804
|
+
actReference: response.actReference,
|
|
2805
|
+
tagUid: response.tagUid,
|
|
2806
|
+
}
|
|
2807
|
+
} finally {
|
|
2808
|
+
// Clean up progress listener
|
|
2809
|
+
if (progressListener) {
|
|
2810
|
+
window.removeEventListener("message", progressListener)
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
/**
|
|
2816
|
+
* Downloads ACT-protected data from the Swarm network.
|
|
2817
|
+
*
|
|
2818
|
+
* This method decrypts the ACT to recover the content reference,
|
|
2819
|
+
* then downloads and returns the decrypted data. Only authorized
|
|
2820
|
+
* grantees (including the publisher) can successfully decrypt.
|
|
2821
|
+
*
|
|
2822
|
+
* @param encryptedReference - The encrypted reference from actUploadData
|
|
2823
|
+
* @param historyReference - The history reference from actUploadData
|
|
2824
|
+
* @param publisherPubKey - The publisher's compressed public key from actUploadData
|
|
2825
|
+
* @param timestamp - Optional timestamp to look up a specific ACT version
|
|
2826
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
2827
|
+
* @returns A promise resolving to the decrypted data as a Uint8Array
|
|
2828
|
+
* @throws {Error} If the client is not initialized
|
|
2829
|
+
* @throws {Error} If the user is not authorized to decrypt the ACT
|
|
2830
|
+
* @throws {Error} If the references are not found
|
|
2831
|
+
* @throws {Error} If the request times out
|
|
2832
|
+
*
|
|
2833
|
+
* @example
|
|
2834
|
+
* ```typescript
|
|
2835
|
+
* // Using the references from actUploadData
|
|
2836
|
+
* const data = await client.actDownloadData(
|
|
2837
|
+
* encryptedReference,
|
|
2838
|
+
* historyReference,
|
|
2839
|
+
* publisherPubKey
|
|
2840
|
+
* )
|
|
2841
|
+
* const text = new TextDecoder().decode(data)
|
|
2842
|
+
* console.log('Decrypted:', text)
|
|
2843
|
+
* ```
|
|
2844
|
+
*/
|
|
2845
|
+
async actDownloadData(
|
|
2846
|
+
encryptedReference: string,
|
|
2847
|
+
historyReference: string,
|
|
2848
|
+
publisherPubKey: string,
|
|
2849
|
+
timestamp?: number,
|
|
2850
|
+
requestOptions?: RequestOptions,
|
|
2851
|
+
): Promise<Uint8Array> {
|
|
2852
|
+
this.ensureReady()
|
|
2853
|
+
const requestId = this.generateRequestId()
|
|
2854
|
+
|
|
2855
|
+
const response = await this.sendRequest<{
|
|
2856
|
+
type: "actDownloadDataResponse"
|
|
2857
|
+
requestId: string
|
|
2858
|
+
data: Uint8Array
|
|
2859
|
+
}>({
|
|
2860
|
+
type: "actDownloadData",
|
|
2861
|
+
requestId,
|
|
2862
|
+
encryptedReference,
|
|
2863
|
+
historyReference,
|
|
2864
|
+
publisherPubKey,
|
|
2865
|
+
timestamp,
|
|
2866
|
+
requestOptions,
|
|
2867
|
+
})
|
|
2868
|
+
|
|
2869
|
+
return response.data
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
/**
|
|
2873
|
+
* Adds new grantees to an existing ACT.
|
|
2874
|
+
*
|
|
2875
|
+
* This method adds new public keys to the ACT's access list.
|
|
2876
|
+
* Only the publisher (original uploader) can add grantees.
|
|
2877
|
+
* Returns new references since Swarm content is immutable.
|
|
2878
|
+
*
|
|
2879
|
+
* @param historyReference - The current history reference
|
|
2880
|
+
* @param grantees - Array of new grantee public keys as compressed hex strings
|
|
2881
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
2882
|
+
* @returns A promise resolving to the new references
|
|
2883
|
+
* @returns return.historyReference - The new history reference after adding grantees
|
|
2884
|
+
* @returns return.granteeListReference - The new grantee list reference
|
|
2885
|
+
* @returns return.actReference - The new ACT reference
|
|
2886
|
+
* @throws {Error} If the client is not initialized
|
|
2887
|
+
* @throws {Error} If the user is not the publisher
|
|
2888
|
+
* @throws {Error} If the request times out
|
|
2889
|
+
*
|
|
2890
|
+
* @example
|
|
2891
|
+
* ```typescript
|
|
2892
|
+
* const newGrantees = ['03d4e5f6...'] // New public keys to grant access
|
|
2893
|
+
* const result = await client.actAddGrantees(historyReference, newGrantees)
|
|
2894
|
+
* console.log('New History Reference:', result.historyReference)
|
|
2895
|
+
* // The encrypted reference remains the same
|
|
2896
|
+
* ```
|
|
2897
|
+
*/
|
|
2898
|
+
async actAddGrantees(
|
|
2899
|
+
historyReference: string,
|
|
2900
|
+
grantees: string[],
|
|
2901
|
+
requestOptions?: RequestOptions,
|
|
2902
|
+
): Promise<{
|
|
2903
|
+
historyReference: string
|
|
2904
|
+
granteeListReference: string
|
|
2905
|
+
actReference: string
|
|
2906
|
+
}> {
|
|
2907
|
+
this.ensureReady()
|
|
2908
|
+
const requestId = this.generateRequestId()
|
|
2909
|
+
|
|
2910
|
+
const response = await this.sendRequest<{
|
|
2911
|
+
type: "actAddGranteesResponse"
|
|
2912
|
+
requestId: string
|
|
2913
|
+
historyReference: string
|
|
2914
|
+
granteeListReference: string
|
|
2915
|
+
actReference: string
|
|
2916
|
+
}>({
|
|
2917
|
+
type: "actAddGrantees",
|
|
2918
|
+
requestId,
|
|
2919
|
+
historyReference,
|
|
2920
|
+
grantees,
|
|
2921
|
+
requestOptions,
|
|
2922
|
+
})
|
|
2923
|
+
|
|
2924
|
+
return {
|
|
2925
|
+
historyReference: response.historyReference,
|
|
2926
|
+
granteeListReference: response.granteeListReference,
|
|
2927
|
+
actReference: response.actReference,
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
/**
|
|
2932
|
+
* Revokes grantees from an existing ACT.
|
|
2933
|
+
*
|
|
2934
|
+
* This method removes public keys from the ACT's access list and performs
|
|
2935
|
+
* key rotation to ensure revoked grantees cannot decrypt new versions.
|
|
2936
|
+
* Returns new references including a new encrypted reference.
|
|
2937
|
+
*
|
|
2938
|
+
* IMPORTANT: The original encrypted reference can still be decrypted by
|
|
2939
|
+
* revoked grantees if they have cached it. Key rotation only protects
|
|
2940
|
+
* access through the new references.
|
|
2941
|
+
*
|
|
2942
|
+
* @param historyReference - The current history reference
|
|
2943
|
+
* @param encryptedReference - The current encrypted reference (needed for key rotation)
|
|
2944
|
+
* @param revokeGrantees - Array of grantee public keys to revoke as compressed hex strings
|
|
2945
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
2946
|
+
* @returns A promise resolving to the new references after revocation
|
|
2947
|
+
* @returns return.encryptedReference - The new encrypted reference (key rotated)
|
|
2948
|
+
* @returns return.historyReference - The new history reference after revocation
|
|
2949
|
+
* @returns return.granteeListReference - The new grantee list reference
|
|
2950
|
+
* @returns return.actReference - The new ACT reference after revocation
|
|
2951
|
+
* @throws {Error} If the client is not initialized
|
|
2952
|
+
* @throws {Error} If the user is not the publisher
|
|
2953
|
+
* @throws {Error} If the request times out
|
|
2954
|
+
*
|
|
2955
|
+
* @example
|
|
2956
|
+
* ```typescript
|
|
2957
|
+
* const revokeKeys = ['03a1b2c3...'] // Public keys to revoke
|
|
2958
|
+
* const result = await client.actRevokeGrantees(historyReference, encryptedReference, revokeKeys)
|
|
2959
|
+
* console.log('New History Reference:', result.historyReference)
|
|
2960
|
+
* console.log('New Encrypted Reference:', result.encryptedReference)
|
|
2961
|
+
* // All references are new due to key rotation
|
|
2962
|
+
* ```
|
|
2963
|
+
*/
|
|
2964
|
+
async actRevokeGrantees(
|
|
2965
|
+
historyReference: string,
|
|
2966
|
+
encryptedReference: string,
|
|
2967
|
+
revokeGrantees: string[],
|
|
2968
|
+
requestOptions?: RequestOptions,
|
|
2969
|
+
): Promise<{
|
|
2970
|
+
encryptedReference: string
|
|
2971
|
+
historyReference: string
|
|
2972
|
+
granteeListReference: string
|
|
2973
|
+
actReference: string
|
|
2974
|
+
}> {
|
|
2975
|
+
this.ensureReady()
|
|
2976
|
+
const requestId = this.generateRequestId()
|
|
2977
|
+
|
|
2978
|
+
const response = await this.sendRequest<{
|
|
2979
|
+
type: "actRevokeGranteesResponse"
|
|
2980
|
+
requestId: string
|
|
2981
|
+
encryptedReference: string
|
|
2982
|
+
historyReference: string
|
|
2983
|
+
granteeListReference: string
|
|
2984
|
+
actReference: string
|
|
2985
|
+
}>({
|
|
2986
|
+
type: "actRevokeGrantees",
|
|
2987
|
+
requestId,
|
|
2988
|
+
historyReference,
|
|
2989
|
+
encryptedReference,
|
|
2990
|
+
revokeGrantees,
|
|
2991
|
+
requestOptions,
|
|
2992
|
+
})
|
|
2993
|
+
|
|
2994
|
+
return {
|
|
2995
|
+
encryptedReference: response.encryptedReference,
|
|
2996
|
+
historyReference: response.historyReference,
|
|
2997
|
+
granteeListReference: response.granteeListReference,
|
|
2998
|
+
actReference: response.actReference,
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
/**
|
|
3003
|
+
* Retrieves the list of grantees from an ACT.
|
|
3004
|
+
*
|
|
3005
|
+
* Only the publisher (original uploader) can view the grantee list,
|
|
3006
|
+
* as it is encrypted with the publisher's key.
|
|
3007
|
+
*
|
|
3008
|
+
* @param historyReference - The history reference
|
|
3009
|
+
* @param requestOptions - Optional request configuration (timeout, headers, endlesslyRetry)
|
|
3010
|
+
* @returns A promise resolving to an array of grantee public keys as compressed hex strings
|
|
3011
|
+
* @throws {Error} If the client is not initialized
|
|
3012
|
+
* @throws {Error} If the user is not the publisher
|
|
3013
|
+
* @throws {Error} If the request times out
|
|
3014
|
+
*
|
|
3015
|
+
* @example
|
|
3016
|
+
* ```typescript
|
|
3017
|
+
* const grantees = await client.actGetGrantees(historyReference)
|
|
3018
|
+
* console.log('Current grantees:', grantees.length)
|
|
3019
|
+
* grantees.forEach(pubKey => console.log(' -', pubKey))
|
|
3020
|
+
* ```
|
|
3021
|
+
*/
|
|
3022
|
+
async actGetGrantees(
|
|
3023
|
+
historyReference: string,
|
|
3024
|
+
requestOptions?: RequestOptions,
|
|
3025
|
+
): Promise<string[]> {
|
|
3026
|
+
this.ensureReady()
|
|
3027
|
+
const requestId = this.generateRequestId()
|
|
3028
|
+
|
|
3029
|
+
const response = await this.sendRequest<{
|
|
3030
|
+
type: "actGetGranteesResponse"
|
|
3031
|
+
requestId: string
|
|
3032
|
+
grantees: string[]
|
|
3033
|
+
}>({
|
|
3034
|
+
type: "actGetGrantees",
|
|
3035
|
+
requestId,
|
|
3036
|
+
historyReference,
|
|
3037
|
+
requestOptions,
|
|
3038
|
+
})
|
|
3039
|
+
|
|
3040
|
+
return response.grantees
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// ============================================================================
|
|
3044
|
+
// Cleanup
|
|
3045
|
+
// ============================================================================
|
|
3046
|
+
|
|
3047
|
+
/**
|
|
3048
|
+
* Destroys the client and releases all resources.
|
|
3049
|
+
*
|
|
3050
|
+
* This method should be called when the client is no longer needed.
|
|
3051
|
+
* It performs the following cleanup:
|
|
3052
|
+
* - Cancels all pending requests with an error
|
|
3053
|
+
* - Removes the message event listener
|
|
3054
|
+
* - Removes the iframe from the DOM
|
|
3055
|
+
* - Resets the client to an uninitialized state
|
|
3056
|
+
*
|
|
3057
|
+
* After calling destroy(), the client instance cannot be reused.
|
|
3058
|
+
* Create a new instance if you need to reconnect.
|
|
3059
|
+
*
|
|
3060
|
+
* @example
|
|
3061
|
+
* ```typescript
|
|
3062
|
+
* // Clean up when component unmounts
|
|
3063
|
+
* useEffect(() => {
|
|
3064
|
+
* const client = new SwarmIdClient({ ... })
|
|
3065
|
+
* client.initialize()
|
|
3066
|
+
*
|
|
3067
|
+
* return () => {
|
|
3068
|
+
* client.destroy()
|
|
3069
|
+
* }
|
|
3070
|
+
* }, [])
|
|
3071
|
+
* ```
|
|
3072
|
+
*/
|
|
3073
|
+
destroy(): void {
|
|
3074
|
+
// Clear pending requests
|
|
3075
|
+
this.pendingRequests.forEach((pending) => {
|
|
3076
|
+
clearTimeout(pending.timeoutId)
|
|
3077
|
+
pending.reject(new Error("Client destroyed"))
|
|
3078
|
+
})
|
|
3079
|
+
this.pendingRequests.clear()
|
|
3080
|
+
|
|
3081
|
+
// Remove message listener
|
|
3082
|
+
if (this.messageListener) {
|
|
3083
|
+
window.removeEventListener("message", this.messageListener)
|
|
3084
|
+
this.messageListener = undefined
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// Remove iframe
|
|
3088
|
+
if (this.iframe && this.iframe.parentNode) {
|
|
3089
|
+
this.iframe.parentNode.removeChild(this.iframe)
|
|
3090
|
+
this.iframe = undefined
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
this.ready = false
|
|
3094
|
+
}
|
|
3095
|
+
}
|