@derec-alliance/react-native 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/DeRec.podspec +25 -0
- package/LICENSE +201 -0
- package/README.md +222 -0
- package/android/CMakeLists.txt +38 -0
- package/android/build.gradle +79 -0
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/java/org/derec/DeRecModule.kt +56 -0
- package/android/src/main/java/org/derec/DeRecPackage.kt +24 -0
- package/android/src/main/jni/OnLoad.cpp +39 -0
- package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
- package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
- package/cpp/Convert.cpp +50 -0
- package/cpp/Convert.h +38 -0
- package/cpp/DeRecInstaller.cpp +186 -0
- package/cpp/DeRecInstaller.h +28 -0
- package/cpp/Invoker.h +20 -0
- package/cpp/JsCallbackBridge.cpp +144 -0
- package/cpp/JsCallbackBridge.h +148 -0
- package/cpp/Primitives.cpp +1193 -0
- package/cpp/Primitives.h +39 -0
- package/cpp/ProtocolHost.cpp +613 -0
- package/cpp/ProtocolHost.h +88 -0
- package/cpp/RuntimeInvoker.h +29 -0
- package/cpp/StoreCallbacks.cpp +1293 -0
- package/cpp/StoreCallbacks.h +111 -0
- package/cpp/WorkerThread.cpp +74 -0
- package/cpp/WorkerThread.h +54 -0
- package/cpp/derec_ffi.h +2145 -0
- package/ios/DeRec.h +23 -0
- package/ios/DeRec.mm +39 -0
- package/ios/DeRecFFI.xcframework/Info.plist +48 -0
- package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +2145 -0
- package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +2145 -0
- package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +0 -0
- package/lib/binary.d.ts +19 -0
- package/lib/binary.js +164 -0
- package/lib/codec.d.ts +16 -0
- package/lib/codec.js +80 -0
- package/lib/errors.d.ts +23 -0
- package/lib/errors.js +35 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +27 -0
- package/lib/messages.d.ts +41 -0
- package/lib/messages.js +192 -0
- package/lib/native.d.ts +11 -0
- package/lib/native.js +40 -0
- package/lib/primitives.d.ts +137 -0
- package/lib/primitives.js +329 -0
- package/lib/protocol.d.ts +188 -0
- package/lib/protocol.js +514 -0
- package/lib/types.d.ts +1014 -0
- package/lib/types.js +63 -0
- package/package.json +55 -0
- package/react-native.config.js +19 -0
- package/src/binary.ts +182 -0
- package/src/codec.ts +79 -0
- package/src/errors.ts +45 -0
- package/src/index.ts +7 -0
- package/src/messages.ts +192 -0
- package/src/native.ts +52 -0
- package/src/primitives.ts +770 -0
- package/src/protocol.ts +621 -0
- package/src/types.ts +1040 -0
package/lib/protocol.js
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Copyright (c) 2026 DeRec Alliance. All rights reserved.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.DeRecProtocol = exports.DeRecProtocolBuilder = void 0;
|
|
6
|
+
exports.protocolDiscriminant = protocolDiscriminant;
|
|
7
|
+
exports.buildProtectSecretParams = buildProtectSecretParams;
|
|
8
|
+
exports.buildRestoreParams = buildRestoreParams;
|
|
9
|
+
const codec_1 = require("./codec");
|
|
10
|
+
const native_1 = require("./native");
|
|
11
|
+
const primitives_1 = require("./primitives");
|
|
12
|
+
const types_1 = require("./types");
|
|
13
|
+
/**
|
|
14
|
+
* Event fields carrying `Vec<u8>`.
|
|
15
|
+
*
|
|
16
|
+
* `serde_json` writes them as arrays of decimal byte values, while
|
|
17
|
+
* {@link DeRecEvent} declares them `Uint8Array` — the shape
|
|
18
|
+
* `@derec-alliance/nodejs` produces, where `serde-wasm-bindgen` makes the
|
|
19
|
+
* conversion natively. Without the conversion below the declared type is a
|
|
20
|
+
* lie, and the first thing an application does with `ActionRequired.action` is
|
|
21
|
+
* hand it back to `accept()`, where the native layer reads `.buffer` off it
|
|
22
|
+
* and finds nothing.
|
|
23
|
+
*
|
|
24
|
+
* Unlike the message codec in `./messages`, ids are deliberately left alone:
|
|
25
|
+
* every id on an event is declared as a decimal `string`, not a `bigint`.
|
|
26
|
+
*/
|
|
27
|
+
const EVENT_BYTE_FIELDS = new Set([
|
|
28
|
+
'action',
|
|
29
|
+
'shared_key',
|
|
30
|
+
'committed_share',
|
|
31
|
+
'id',
|
|
32
|
+
'data',
|
|
33
|
+
]);
|
|
34
|
+
function reviveEventBytes(value) {
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value.map(reviveEventBytes);
|
|
37
|
+
}
|
|
38
|
+
if (value === null || typeof value !== 'object') {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [key, field] of Object.entries(value)) {
|
|
43
|
+
out[key] =
|
|
44
|
+
EVENT_BYTE_FIELDS.has(key) && Array.isArray(field)
|
|
45
|
+
? Uint8Array.from(field)
|
|
46
|
+
: reviveEventBytes(field);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function decodeEvents(buffer) {
|
|
51
|
+
return reviveEventBytes((0, codec_1.jsonFromBytes)(buffer));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Maps a transport protocol name to the `derec_proto::Protocol` discriminant.
|
|
55
|
+
* `bindings/test_fixture/enums.json` — the shared enum fixture every other
|
|
56
|
+
* numeric mapping in this SDK is read from — does not carry this enum (it
|
|
57
|
+
* only covers the Rust-internal serde enums listed under its `"enums"` key);
|
|
58
|
+
* the transport `Protocol` enum lives in `protobufs/transportprotocol.proto`
|
|
59
|
+
* instead, where only `HTTPS = 0` is currently defined.
|
|
60
|
+
*/
|
|
61
|
+
function protocolDiscriminant(protocol) {
|
|
62
|
+
switch (protocol.toLowerCase()) {
|
|
63
|
+
case 'https':
|
|
64
|
+
return 0;
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`DeRec: unknown transport protocol "${protocol}"`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function encodeTarget(target) {
|
|
70
|
+
if (target === null) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(target)) {
|
|
74
|
+
return target.map((id) => id.toString());
|
|
75
|
+
}
|
|
76
|
+
return target.toString();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* `ProtectSecretParamsJson` (`library/src/interop/ffi/protocol/flow.rs`): every
|
|
80
|
+
* `UserSecret.id`/`.data` byte array travels as a bare JSON array of decimal
|
|
81
|
+
* byte values, matching `UserSecretJson`'s plain `Vec<u8>` fields — no
|
|
82
|
+
* base64, no wrapping.
|
|
83
|
+
*
|
|
84
|
+
* Exported so it can be golden-tested against
|
|
85
|
+
* `bindings/test_fixture/wire_golden.json` without a native module.
|
|
86
|
+
*/
|
|
87
|
+
function buildProtectSecretParams(params) {
|
|
88
|
+
const out = {
|
|
89
|
+
secrets: params.secrets.map((secret) => ({
|
|
90
|
+
id: Array.from(secret.id),
|
|
91
|
+
name: secret.name,
|
|
92
|
+
data: Array.from(secret.data),
|
|
93
|
+
})),
|
|
94
|
+
};
|
|
95
|
+
if (params.description !== undefined) {
|
|
96
|
+
out.description = params.description;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
function communicationInfoOrOmit(info) {
|
|
101
|
+
return info !== undefined && Object.keys(info).length > 0 ? info : undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `RestoreParamsJson` (`library/src/interop/ffi/protocol/handle/flow.rs`). Mirrors
|
|
105
|
+
* `SecretWire`'s own shape: `channel_id`/`replica_id` are the decimal `u64`
|
|
106
|
+
* strings the `SecretRecovered` event already carries, forwarded verbatim;
|
|
107
|
+
* `communication_info` is omitted per-entry (not sent as `{}`) whenever a
|
|
108
|
+
* helper or replica member has none, matching the Go SDK's `omitempty` and
|
|
109
|
+
* the shared golden fixture byte-for-byte; `replicas` is omitted entirely
|
|
110
|
+
* when the recovered secret has no replica group.
|
|
111
|
+
*
|
|
112
|
+
* Exported so it can be golden-tested against
|
|
113
|
+
* `bindings/test_fixture/wire_golden.json` without a native module.
|
|
114
|
+
*/
|
|
115
|
+
function buildRestoreParams(recoveredSecret, version) {
|
|
116
|
+
const recovered = {
|
|
117
|
+
helpers: recoveredSecret.helpers.map((helper) => {
|
|
118
|
+
const out = {
|
|
119
|
+
channel_id: helper.channel_id,
|
|
120
|
+
transport_uri: helper.transport_uri,
|
|
121
|
+
shared_key: Array.from(helper.shared_key),
|
|
122
|
+
};
|
|
123
|
+
const info = communicationInfoOrOmit(helper.communication_info);
|
|
124
|
+
if (info !== undefined) {
|
|
125
|
+
out.communication_info = info;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}),
|
|
129
|
+
secrets: recoveredSecret.secrets.map((secret) => ({
|
|
130
|
+
id: Array.from(secret.id),
|
|
131
|
+
name: secret.name,
|
|
132
|
+
data: Array.from(secret.data),
|
|
133
|
+
})),
|
|
134
|
+
};
|
|
135
|
+
if (recoveredSecret.replicas !== undefined) {
|
|
136
|
+
recovered.replicas = {
|
|
137
|
+
channel_id: recoveredSecret.replicas.channel_id,
|
|
138
|
+
members: recoveredSecret.replicas.members.map((member) => {
|
|
139
|
+
const out = {
|
|
140
|
+
replica_id: member.replica_id,
|
|
141
|
+
transport_uri: member.transport_uri,
|
|
142
|
+
role: member.role,
|
|
143
|
+
};
|
|
144
|
+
const info = communicationInfoOrOmit(member.communication_info);
|
|
145
|
+
if (info !== undefined) {
|
|
146
|
+
out.communication_info = info;
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}),
|
|
150
|
+
shared_key: Array.from(recoveredSecret.replicas.shared_key),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return { version, recovered_secret: recovered };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* `PairingParamsJson.contact` (`library/src/interop/ffi/protocol/flow.rs`) is a
|
|
157
|
+
* `Vec<u8>` of proto-encoded `ContactMessage` bytes, so the typed contact the
|
|
158
|
+
* caller holds is re-encoded through the library's own codec on the way in.
|
|
159
|
+
* Encoding in Rust also means the contact is structurally validated before
|
|
160
|
+
* the pairing flow ever sees it.
|
|
161
|
+
*/
|
|
162
|
+
function contactToByteArray(contact) {
|
|
163
|
+
return Array.from(primitives_1.primitives.pairing.request.encode_contact(contact));
|
|
164
|
+
}
|
|
165
|
+
function buildStartParams(flowKind, params) {
|
|
166
|
+
switch (flowKind) {
|
|
167
|
+
case types_1.FlowKind.Pairing: {
|
|
168
|
+
const p = params;
|
|
169
|
+
const out = {
|
|
170
|
+
kind: p.kind,
|
|
171
|
+
contact: contactToByteArray(p.contact),
|
|
172
|
+
};
|
|
173
|
+
if (p.peerCommunicationInfo !== undefined) {
|
|
174
|
+
out.peer_communication_info = p.peerCommunicationInfo;
|
|
175
|
+
}
|
|
176
|
+
return (0, codec_1.jsonToBytes)(out);
|
|
177
|
+
}
|
|
178
|
+
case types_1.FlowKind.Discovery: {
|
|
179
|
+
const p = (params ?? {});
|
|
180
|
+
const out = {};
|
|
181
|
+
if (p.target !== undefined) {
|
|
182
|
+
out.target = encodeTarget(p.target);
|
|
183
|
+
}
|
|
184
|
+
return (0, codec_1.jsonToBytes)(out);
|
|
185
|
+
}
|
|
186
|
+
case types_1.FlowKind.ProtectSecret:
|
|
187
|
+
return (0, codec_1.jsonToBytes)(buildProtectSecretParams(params));
|
|
188
|
+
case types_1.FlowKind.VerifyShares: {
|
|
189
|
+
const p = params;
|
|
190
|
+
const out = {
|
|
191
|
+
secret_id: p.secretId.toString(),
|
|
192
|
+
version: p.version,
|
|
193
|
+
};
|
|
194
|
+
if (p.target !== undefined) {
|
|
195
|
+
out.target = encodeTarget(p.target);
|
|
196
|
+
}
|
|
197
|
+
return (0, codec_1.jsonToBytes)(out);
|
|
198
|
+
}
|
|
199
|
+
case types_1.FlowKind.RecoverSecret: {
|
|
200
|
+
const p = params;
|
|
201
|
+
return (0, codec_1.jsonToBytes)({ secret_id: p.secretId.toString(), version: p.version });
|
|
202
|
+
}
|
|
203
|
+
case types_1.FlowKind.Unpair: {
|
|
204
|
+
const p = params;
|
|
205
|
+
const out = { channel_id: p.channel_id };
|
|
206
|
+
if (p.memo !== undefined) {
|
|
207
|
+
out.memo = p.memo;
|
|
208
|
+
}
|
|
209
|
+
return (0, codec_1.jsonToBytes)(out);
|
|
210
|
+
}
|
|
211
|
+
case types_1.FlowKind.UpdateChannelInfo: {
|
|
212
|
+
const p = (params ?? {});
|
|
213
|
+
const out = {};
|
|
214
|
+
if (p.target !== undefined) {
|
|
215
|
+
out.target = encodeTarget(p.target);
|
|
216
|
+
}
|
|
217
|
+
if (p.communication_info !== undefined) {
|
|
218
|
+
out.communication_info = p.communication_info;
|
|
219
|
+
}
|
|
220
|
+
if (p.transport_protocol !== undefined) {
|
|
221
|
+
out.transport_protocol = p.transport_protocol;
|
|
222
|
+
}
|
|
223
|
+
return (0, codec_1.jsonToBytes)(out);
|
|
224
|
+
}
|
|
225
|
+
case types_1.FlowKind.SyncCheck:
|
|
226
|
+
// No parameters: the group and this device's own version both come
|
|
227
|
+
// from the stores.
|
|
228
|
+
return (0, codec_1.jsonToBytes)({});
|
|
229
|
+
case types_1.FlowKind.RemoveReplica: {
|
|
230
|
+
const p = params;
|
|
231
|
+
const out = { replica_id: p.replica_id };
|
|
232
|
+
if (p.memo !== undefined) {
|
|
233
|
+
out.memo = p.memo;
|
|
234
|
+
}
|
|
235
|
+
return (0, codec_1.jsonToBytes)(out);
|
|
236
|
+
}
|
|
237
|
+
default: {
|
|
238
|
+
const exhaustive = flowKind;
|
|
239
|
+
throw new Error(`DeRec: unknown FlowKind ${exhaustive}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function toBigInt(id) {
|
|
244
|
+
return typeof id === 'bigint' ? id : BigInt(id);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Builder for {@link DeRecProtocol}, mirroring `@derec-alliance/nodejs`'s
|
|
248
|
+
* `DeRecProtocolBuilder` method-for-method. The config JSON shape is dictated
|
|
249
|
+
* by the Rust `ProtocolConfig` struct (`library/src/interop/ffi/protocol/handle/mod.rs`);
|
|
250
|
+
* every field here writes exactly the `snake_case` key that struct expects.
|
|
251
|
+
*
|
|
252
|
+
* Every value the caller supplies is forwarded verbatim — defaults, clamping
|
|
253
|
+
* and the meaning of a disabled cleanup policy are library decisions.
|
|
254
|
+
* `threshold`, `keep_versions_count`, `auto_respond_on_failure`,
|
|
255
|
+
* `unpair_ack`, `auto_reply_to` and `auto_accept` are omitted entirely from
|
|
256
|
+
* the emitted config unless their setter was called: `ProtocolConfig` has a
|
|
257
|
+
* `#[serde(default = "...")]` on each of them, reading the same constants
|
|
258
|
+
* `DeRecProtocolBuilder::new` uses, so an absent key resolves to the
|
|
259
|
+
* library's own default rather than one frozen into this shim.
|
|
260
|
+
*/
|
|
261
|
+
class DeRecProtocolBuilder {
|
|
262
|
+
constructor(secretId) {
|
|
263
|
+
this.config = {};
|
|
264
|
+
this.stores = {};
|
|
265
|
+
this.secretIdValue = BigInt(secretId);
|
|
266
|
+
}
|
|
267
|
+
withChannelStore(store) {
|
|
268
|
+
this.stores.channelStore = store;
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
withShareStore(store) {
|
|
272
|
+
this.stores.shareStore = store;
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
withSecretStore(store) {
|
|
276
|
+
this.stores.secretStore = store;
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
withUserSecretStore(store) {
|
|
280
|
+
this.stores.userSecretStore = store;
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
withStateStore(store) {
|
|
284
|
+
this.stores.stateStore = store;
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
withTransport(transport) {
|
|
288
|
+
this.stores.transport = transport;
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
withOwnTransport(endpoint) {
|
|
292
|
+
this.config.own_transport_uri = endpoint.uri;
|
|
293
|
+
this.config.own_transport_protocol = protocolDiscriminant(endpoint.protocol);
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
/** Default: 3. */
|
|
297
|
+
withThreshold(threshold) {
|
|
298
|
+
this.config.threshold = threshold;
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
/** Default: 3. */
|
|
302
|
+
withKeepVersionsCount(count) {
|
|
303
|
+
this.config.keep_versions_count = count;
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Every field is optional; not calling this at all leaves all four at the
|
|
308
|
+
* library's defaults — `timeouts` is genuinely absent from the JSON in
|
|
309
|
+
* that case, not filled in with a synthesized object. Values are forwarded
|
|
310
|
+
* verbatim; `Timeouts`'s field names already match `TimeoutsConfig`'s
|
|
311
|
+
* `snake_case` keys one for one.
|
|
312
|
+
*/
|
|
313
|
+
withTimeouts(timeouts) {
|
|
314
|
+
this.config.timeouts = { ...timeouts };
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
/** Default: false. */
|
|
318
|
+
withUnsafeHttp(allow) {
|
|
319
|
+
this.config.unsafe_http = allow;
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Default: empty. `derec_protocol_new`'s own `communication_info` argument
|
|
324
|
+
* is a pre-encoded `CommunicationInfo` protobuf message, and this SDK has
|
|
325
|
+
* no encoder for that message — the C ABI exposes one only for
|
|
326
|
+
* `ContactMessage`. Hand-rolling one here would be SDK-side protocol logic
|
|
327
|
+
* with no test coverage — exactly what this codebase's "Rust decides, the
|
|
328
|
+
* SDK only marshals" rule forbids. Instead,
|
|
329
|
+
* `build()` constructs the protocol with an empty `communication_info`
|
|
330
|
+
* (a legitimate, always-accepted value — see `decode_communication_info`
|
|
331
|
+
* in `library/src/interop/ffi/protocol/handle/mod.rs`, which treats a zero-length
|
|
332
|
+
* buffer as "no entries") and then calls the already-bound
|
|
333
|
+
* {@link DeRecProtocol.setCommunicationInfo}, which takes the same map as
|
|
334
|
+
* plain JSON (`derec_protocol_set_communication_info` in
|
|
335
|
+
* `library/src/interop/ffi/protocol/handle/config.rs`). That setter is queued on
|
|
336
|
+
* the protocol's serial worker before `build()` returns, so it is ordered
|
|
337
|
+
* ahead of every call the caller can subsequently make.
|
|
338
|
+
*/
|
|
339
|
+
withCommunicationInfo(info) {
|
|
340
|
+
this.communicationInfo = info;
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
/** Default: false. */
|
|
344
|
+
withAutoRespondOnFailure(enabled) {
|
|
345
|
+
this.config.auto_respond_on_failure = enabled;
|
|
346
|
+
return this;
|
|
347
|
+
}
|
|
348
|
+
/** Default: "required". */
|
|
349
|
+
withUnpairAck(ack) {
|
|
350
|
+
if (ack === 'required') {
|
|
351
|
+
this.config.unpair_ack = 0;
|
|
352
|
+
}
|
|
353
|
+
else if (ack === 'not_required') {
|
|
354
|
+
this.config.unpair_ack = 1;
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
throw new Error(`DeRec: unknown unpair ack "${ack}"`);
|
|
358
|
+
}
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
/** Default: false. */
|
|
362
|
+
withAutoReplyTo(enabled) {
|
|
363
|
+
this.config.auto_reply_to = enabled;
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
/** Default: empty policy (every flow off). */
|
|
367
|
+
withAutoAccept(policy) {
|
|
368
|
+
this.config.auto_accept = {
|
|
369
|
+
pairing: policy.pairing ?? false,
|
|
370
|
+
pre_pair: policy.prePair ?? false,
|
|
371
|
+
store_share: policy.storeShare ?? false,
|
|
372
|
+
verify_share: policy.verifyShare ?? false,
|
|
373
|
+
discovery: policy.discovery ?? false,
|
|
374
|
+
get_share: policy.getShare ?? false,
|
|
375
|
+
unpair: policy.unpair ?? false,
|
|
376
|
+
update_channel_info: policy.updateChannelInfo ?? false,
|
|
377
|
+
};
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
/** Default: unset. */
|
|
381
|
+
withReplicaId(replicaId) {
|
|
382
|
+
this.config.replica_id = BigInt(replicaId).toString();
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
build() {
|
|
386
|
+
const config = {
|
|
387
|
+
secret_id: this.secretIdValue.toString(),
|
|
388
|
+
replica_id: null,
|
|
389
|
+
own_transport_uri: '',
|
|
390
|
+
own_transport_protocol: 0,
|
|
391
|
+
...this.config,
|
|
392
|
+
};
|
|
393
|
+
const host = (0, native_1.getNative)().protocol_new(JSON.stringify(config), new Uint8Array(0), this.stores);
|
|
394
|
+
const protocol = DeRecProtocol.fromHost(host);
|
|
395
|
+
if (this.communicationInfo !== undefined) {
|
|
396
|
+
// `setCommunicationInfo` is asynchronous, but `build()` stays
|
|
397
|
+
// synchronous: the guarantee callers need is not that the map has been
|
|
398
|
+
// applied by the time `build()` returns, it is that no flow can run
|
|
399
|
+
// before it. Posting it here puts it on the protocol's serial worker
|
|
400
|
+
// queue ahead of anything the caller has had the chance to start, so
|
|
401
|
+
// every subsequent call observes it. Left unhandled deliberately — a
|
|
402
|
+
// failure reaches the runtime's unhandled-rejection reporting rather
|
|
403
|
+
// than being silently discarded, and `build()` has no synchronous
|
|
404
|
+
// channel to raise it on.
|
|
405
|
+
void protocol.setCommunicationInfo(this.communicationInfo);
|
|
406
|
+
}
|
|
407
|
+
return protocol;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
exports.DeRecProtocolBuilder = DeRecProtocolBuilder;
|
|
411
|
+
/**
|
|
412
|
+
* Thin wrapper around the `ProtocolHost` JSI host object, mirroring
|
|
413
|
+
* `@derec-alliance/nodejs`'s `DeRecProtocol` — same methods, and the same
|
|
414
|
+
* sync/async split except for `setCommunicationInfo` and `setOwnTransport`,
|
|
415
|
+
* which return a `Promise` here (see their own doc comments for why);
|
|
416
|
+
* `secretId` is synchronous and everything else returns a `Promise`. Every method encodes its
|
|
417
|
+
* arguments into the wire shape the host function expects and decodes its
|
|
418
|
+
* result back into the typed value declared here; no protocol decision is
|
|
419
|
+
* made in this class.
|
|
420
|
+
*/
|
|
421
|
+
class DeRecProtocol {
|
|
422
|
+
/** Use {@link DeRecProtocolBuilder} to construct instances. */
|
|
423
|
+
constructor(host) {
|
|
424
|
+
this.host = host;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* @internal Constructs an instance from a native `ProtocolHost`. Not part
|
|
428
|
+
* of the public surface — {@link DeRecProtocolBuilder.build} is the only
|
|
429
|
+
* caller. Exists because the constructor is `private` (matching
|
|
430
|
+
* `@derec-alliance/nodejs`'s `index.d.ts`), and a private constructor is
|
|
431
|
+
* only callable from within this class's own body, not from
|
|
432
|
+
* `DeRecProtocolBuilder`.
|
|
433
|
+
*/
|
|
434
|
+
static fromHost(host) {
|
|
435
|
+
return new DeRecProtocol(host);
|
|
436
|
+
}
|
|
437
|
+
secretId() {
|
|
438
|
+
return this.host.secretId();
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Returns a `Promise`, where `@derec-alliance/nodejs` declares this
|
|
442
|
+
* `void`. The native setter takes the same handle mutex a running flow
|
|
443
|
+
* holds across its store callbacks, so calling it on the JavaScript thread
|
|
444
|
+
* would block that thread until the flow released the mutex — which it
|
|
445
|
+
* cannot do while it is waiting on the JavaScript thread. The binding
|
|
446
|
+
* therefore queues the setter onto the same serial worker every flow runs
|
|
447
|
+
* on, which orders it after any call already in flight. The nodejs SDK is
|
|
448
|
+
* WASM and single-threaded, so it has no such hazard.
|
|
449
|
+
*
|
|
450
|
+
* Callers that ignore the result behave exactly as before, so this stays
|
|
451
|
+
* source-compatible with nodejs-shaped code; awaiting it additionally
|
|
452
|
+
* surfaces a failure that would otherwise be swallowed.
|
|
453
|
+
*/
|
|
454
|
+
setCommunicationInfo(info) {
|
|
455
|
+
return this.host.setCommunicationInfo((0, codec_1.jsonToBytes)(info));
|
|
456
|
+
}
|
|
457
|
+
/** Returns a `Promise` for the same reason {@link setCommunicationInfo} does. */
|
|
458
|
+
setOwnTransport(uri, protocol) {
|
|
459
|
+
return this.host.setOwnTransport(uri, protocolDiscriminant(protocol));
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Decodes the `contact_wire_bytes` `derec_protocol_create_contact`
|
|
463
|
+
* produces into a typed `ContactMessage`, so an application can read the
|
|
464
|
+
* contact's `nonce`, `transport_protocol` and mode without a protobuf
|
|
465
|
+
* codec of its own. Feed the returned object straight back into
|
|
466
|
+
* `start(FlowKind.Pairing, { contact })`, which re-encodes it.
|
|
467
|
+
*/
|
|
468
|
+
async createContact(channelId, contactMode, nonce) {
|
|
469
|
+
const hasChannelId = channelId !== null && channelId !== undefined ? 1 : 0;
|
|
470
|
+
const hasNonce = nonce !== null && nonce !== undefined ? 1 : 0;
|
|
471
|
+
const buffer = (await this.host.createContact(hasChannelId, toBigInt(channelId ?? 0), contactMode, hasNonce, toBigInt(nonce ?? 0)));
|
|
472
|
+
return primitives_1.primitives.pairing.request.decode_contact(new Uint8Array(buffer));
|
|
473
|
+
}
|
|
474
|
+
async start(flowKind, params) {
|
|
475
|
+
const paramsBytes = buildStartParams(flowKind, params);
|
|
476
|
+
const buffer = (await this.host.start(flowKind, paramsBytes));
|
|
477
|
+
return decodeEvents(buffer);
|
|
478
|
+
}
|
|
479
|
+
async process(message) {
|
|
480
|
+
const buffer = (await this.host.process(message));
|
|
481
|
+
return decodeEvents(buffer);
|
|
482
|
+
}
|
|
483
|
+
async tick() {
|
|
484
|
+
const buffer = (await this.host.tick());
|
|
485
|
+
return decodeEvents(buffer);
|
|
486
|
+
}
|
|
487
|
+
async accept(actionBytes) {
|
|
488
|
+
const buffer = (await this.host.accept(actionBytes));
|
|
489
|
+
return decodeEvents(buffer);
|
|
490
|
+
}
|
|
491
|
+
async reject(actionBytes, status, memo) {
|
|
492
|
+
await this.host.reject(actionBytes, status, memo);
|
|
493
|
+
}
|
|
494
|
+
async getFingerprint(channelId) {
|
|
495
|
+
return (await this.host.getFingerprint(toBigInt(channelId)));
|
|
496
|
+
}
|
|
497
|
+
async verifyFingerprint(channelId, fingerprint) {
|
|
498
|
+
return (await this.host.verifyFingerprint(toBigInt(channelId), fingerprint));
|
|
499
|
+
}
|
|
500
|
+
async removeExpiredChannels(olderThanSecs) {
|
|
501
|
+
const buffer = (await this.host.removeExpiredChannels(olderThanSecs));
|
|
502
|
+
return (0, codec_1.jsonFromBytes)(buffer);
|
|
503
|
+
}
|
|
504
|
+
async restore(recoveredSecret, version) {
|
|
505
|
+
const params = buildRestoreParams(recoveredSecret, version);
|
|
506
|
+
const buffer = (await this.host.restore((0, codec_1.jsonToBytes)(params)));
|
|
507
|
+
return decodeEvents(buffer);
|
|
508
|
+
}
|
|
509
|
+
/** Release the native handle. Safe to call more than once. */
|
|
510
|
+
free() {
|
|
511
|
+
this.host.free();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
exports.DeRecProtocol = DeRecProtocol;
|