@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.
Files changed (65) hide show
  1. package/DeRec.podspec +25 -0
  2. package/LICENSE +201 -0
  3. package/README.md +222 -0
  4. package/android/CMakeLists.txt +38 -0
  5. package/android/build.gradle +79 -0
  6. package/android/src/main/AndroidManifest.xml +12 -0
  7. package/android/src/main/java/org/derec/DeRecModule.kt +56 -0
  8. package/android/src/main/java/org/derec/DeRecPackage.kt +24 -0
  9. package/android/src/main/jni/OnLoad.cpp +39 -0
  10. package/android/src/main/jniLibs/arm64-v8a/libderec_library.a +0 -0
  11. package/android/src/main/jniLibs/armeabi-v7a/libderec_library.a +0 -0
  12. package/android/src/main/jniLibs/x86_64/libderec_library.a +0 -0
  13. package/cpp/Convert.cpp +50 -0
  14. package/cpp/Convert.h +38 -0
  15. package/cpp/DeRecInstaller.cpp +186 -0
  16. package/cpp/DeRecInstaller.h +28 -0
  17. package/cpp/Invoker.h +20 -0
  18. package/cpp/JsCallbackBridge.cpp +144 -0
  19. package/cpp/JsCallbackBridge.h +148 -0
  20. package/cpp/Primitives.cpp +1193 -0
  21. package/cpp/Primitives.h +39 -0
  22. package/cpp/ProtocolHost.cpp +613 -0
  23. package/cpp/ProtocolHost.h +88 -0
  24. package/cpp/RuntimeInvoker.h +29 -0
  25. package/cpp/StoreCallbacks.cpp +1293 -0
  26. package/cpp/StoreCallbacks.h +111 -0
  27. package/cpp/WorkerThread.cpp +74 -0
  28. package/cpp/WorkerThread.h +54 -0
  29. package/cpp/derec_ffi.h +2145 -0
  30. package/ios/DeRec.h +23 -0
  31. package/ios/DeRec.mm +39 -0
  32. package/ios/DeRecFFI.xcframework/Info.plist +48 -0
  33. package/ios/DeRecFFI.xcframework/ios-arm64/Headers/derec_ffi.h +2145 -0
  34. package/ios/DeRecFFI.xcframework/ios-arm64/libderec_library.a +0 -0
  35. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/Headers/derec_ffi.h +2145 -0
  36. package/ios/DeRecFFI.xcframework/ios-arm64_x86_64-simulator/libderec_library.a +0 -0
  37. package/lib/binary.d.ts +19 -0
  38. package/lib/binary.js +164 -0
  39. package/lib/codec.d.ts +16 -0
  40. package/lib/codec.js +80 -0
  41. package/lib/errors.d.ts +23 -0
  42. package/lib/errors.js +35 -0
  43. package/lib/index.d.ts +4 -0
  44. package/lib/index.js +27 -0
  45. package/lib/messages.d.ts +41 -0
  46. package/lib/messages.js +192 -0
  47. package/lib/native.d.ts +11 -0
  48. package/lib/native.js +40 -0
  49. package/lib/primitives.d.ts +137 -0
  50. package/lib/primitives.js +329 -0
  51. package/lib/protocol.d.ts +188 -0
  52. package/lib/protocol.js +514 -0
  53. package/lib/types.d.ts +1014 -0
  54. package/lib/types.js +63 -0
  55. package/package.json +55 -0
  56. package/react-native.config.js +19 -0
  57. package/src/binary.ts +182 -0
  58. package/src/codec.ts +79 -0
  59. package/src/errors.ts +45 -0
  60. package/src/index.ts +7 -0
  61. package/src/messages.ts +192 -0
  62. package/src/native.ts +52 -0
  63. package/src/primitives.ts +770 -0
  64. package/src/protocol.ts +621 -0
  65. package/src/types.ts +1040 -0
@@ -0,0 +1,19 @@
1
+ import type { SecretVersionEntry } from './types';
2
+ /** Discovery secret list — see `library/src/interop/ffi/discovery.rs`. */
3
+ export declare function encodeSecretList(entries: SecretVersionEntry[]): Uint8Array;
4
+ export declare function decodeSecretList(buffer: Uint8Array | ArrayBuffer): SecretVersionEntry[];
5
+ /**
6
+ * Recovery response set — see `library/src/interop/ffi/recovery.rs`. Each
7
+ * entry is a serialized `GetShareResponseMessage`, which the caller obtained
8
+ * from `extract_get_share_response`.
9
+ */
10
+ export declare function encodeShareResponses(responses: Uint8Array[]): Uint8Array;
11
+ /**
12
+ * Committed-share map — see `library/src/interop/ffi/sharing.rs`. Entries
13
+ * arrive sorted by channel id; the `Map` preserves that insertion order, so
14
+ * iterating the result is deterministic.
15
+ */
16
+ export declare function decodeCommittedShareEntries(buffer: Uint8Array | ArrayBuffer): Array<{
17
+ channelId: bigint;
18
+ shareProto: Uint8Array;
19
+ }>;
package/lib/binary.js ADDED
@@ -0,0 +1,164 @@
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.encodeSecretList = encodeSecretList;
6
+ exports.decodeSecretList = decodeSecretList;
7
+ exports.encodeShareResponses = encodeShareResponses;
8
+ exports.decodeCommittedShareEntries = decodeCommittedShareEntries;
9
+ /**
10
+ * The three FFI container formats.
11
+ *
12
+ * Most values cross this boundary as protobuf or JSON. Three do not: the C ABI
13
+ * ferries a set of items as a single hand-packed little-endian buffer, because
14
+ * a C signature cannot carry a variable-length array of variable-length items
15
+ * without one. Each format is specified in the module header of the Rust file
16
+ * that owns it, and the encoders below follow those specs exactly:
17
+ *
18
+ * - the discovery secret list (`library/src/interop/ffi/discovery.rs`)
19
+ * - the recovery response set (`library/src/interop/ffi/recovery.rs`)
20
+ * - the committed-share map (`library/src/interop/ffi/sharing.rs`)
21
+ *
22
+ * Everything here is representation, not protocol: no field is defaulted,
23
+ * reordered or interpreted.
24
+ */
25
+ const codec_1 = require("./codec");
26
+ class Writer {
27
+ constructor() {
28
+ this.chunks = [];
29
+ }
30
+ u32(value) {
31
+ const buffer = new Uint8Array(4);
32
+ new DataView(buffer.buffer).setUint32(0, value, true);
33
+ this.chunks.push(buffer);
34
+ }
35
+ u64(value) {
36
+ const buffer = new Uint8Array(8);
37
+ new DataView(buffer.buffer).setBigUint64(0, value, true);
38
+ this.chunks.push(buffer);
39
+ }
40
+ bytes(value) {
41
+ this.chunks.push(value);
42
+ }
43
+ finish() {
44
+ let length = 0;
45
+ for (const chunk of this.chunks) {
46
+ length += chunk.length;
47
+ }
48
+ const out = new Uint8Array(length);
49
+ let offset = 0;
50
+ for (const chunk of this.chunks) {
51
+ out.set(chunk, offset);
52
+ offset += chunk.length;
53
+ }
54
+ return out;
55
+ }
56
+ }
57
+ class Reader {
58
+ constructor(data) {
59
+ this.data = data;
60
+ this.offset = 0;
61
+ this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
62
+ }
63
+ require(count) {
64
+ if (this.offset + count > this.data.length) {
65
+ // A truncated container means the SDK and the library disagree about
66
+ // the format. Failing loudly beats returning a half-read set that a
67
+ // caller would take for the whole thing.
68
+ throw new Error(`DeRec: malformed FFI container — wanted ${count} bytes at offset ${this.offset}, have ${this.data.length - this.offset}`);
69
+ }
70
+ }
71
+ u32() {
72
+ this.require(4);
73
+ const value = this.view.getUint32(this.offset, true);
74
+ this.offset += 4;
75
+ return value;
76
+ }
77
+ u64() {
78
+ this.require(8);
79
+ const value = this.view.getBigUint64(this.offset, true);
80
+ this.offset += 8;
81
+ return value;
82
+ }
83
+ bytes(count) {
84
+ this.require(count);
85
+ const value = this.data.subarray(this.offset, this.offset + count);
86
+ this.offset += count;
87
+ return value;
88
+ }
89
+ atEnd() {
90
+ return this.offset === this.data.length;
91
+ }
92
+ }
93
+ function asBytes(buffer) {
94
+ return buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
95
+ }
96
+ /** Discovery secret list — see `library/src/interop/ffi/discovery.rs`. */
97
+ function encodeSecretList(entries) {
98
+ const writer = new Writer();
99
+ writer.u32(entries.length);
100
+ for (const entry of entries) {
101
+ writer.u64(entry.secret_id);
102
+ writer.u32(entry.versions.length);
103
+ for (const version of entry.versions) {
104
+ writer.u32(version.version);
105
+ const description = (0, codec_1.utf8Encode)(version.description);
106
+ writer.u32(description.length);
107
+ writer.bytes(description);
108
+ }
109
+ }
110
+ return writer.finish();
111
+ }
112
+ function decodeSecretList(buffer) {
113
+ const reader = new Reader(asBytes(buffer));
114
+ const count = reader.u32();
115
+ const entries = [];
116
+ for (let i = 0; i < count; i++) {
117
+ const secretId = reader.u64();
118
+ const versionCount = reader.u32();
119
+ const versions = [];
120
+ for (let v = 0; v < versionCount; v++) {
121
+ const version = reader.u32();
122
+ const descriptionLength = reader.u32();
123
+ versions.push({
124
+ version,
125
+ description: (0, codec_1.utf8Decode)(reader.bytes(descriptionLength)),
126
+ });
127
+ }
128
+ entries.push({ secret_id: secretId, versions });
129
+ }
130
+ return entries;
131
+ }
132
+ /**
133
+ * Recovery response set — see `library/src/interop/ffi/recovery.rs`. Each
134
+ * entry is a serialized `GetShareResponseMessage`, which the caller obtained
135
+ * from `extract_get_share_response`.
136
+ */
137
+ function encodeShareResponses(responses) {
138
+ const writer = new Writer();
139
+ writer.u32(responses.length);
140
+ for (const response of responses) {
141
+ writer.u32(response.length);
142
+ writer.bytes(response);
143
+ }
144
+ return writer.finish();
145
+ }
146
+ /**
147
+ * Committed-share map — see `library/src/interop/ffi/sharing.rs`. Entries
148
+ * arrive sorted by channel id; the `Map` preserves that insertion order, so
149
+ * iterating the result is deterministic.
150
+ */
151
+ function decodeCommittedShareEntries(buffer) {
152
+ const reader = new Reader(asBytes(buffer));
153
+ const count = reader.u32();
154
+ const entries = [];
155
+ for (let i = 0; i < count; i++) {
156
+ const channelId = reader.u64();
157
+ const shareLength = reader.u32();
158
+ entries.push({ channelId, shareProto: reader.bytes(shareLength) });
159
+ }
160
+ if (!reader.atEnd()) {
161
+ throw new Error('DeRec: trailing bytes after the committed-share map');
162
+ }
163
+ return entries;
164
+ }
package/lib/codec.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * UTF-8 byte <-> string codec, shared by every JSON payload that crosses the
3
+ * JSI boundary (config, flow params, the events array, the `ContactMessage`
4
+ * JSON the pairing codec entry points exchange). Those payloads travel as
5
+ * bytes, not a JS string — the host functions read `ArrayBuffer`/typed-array
6
+ * arguments and hand back `ArrayBuffer` results (see `ProtocolHost.cpp`'s
7
+ * `bytesToArrayBuffer`). `TextDecoder`/`TextEncoder` are not declared as
8
+ * ambient globals under this package's `lib: ["ES2020"]` (no DOM lib) and the
9
+ * installed `@types/node` exposes them only from `node:util`, which does not
10
+ * exist in the Hermes runtime this code actually ships on. A small
11
+ * dependency-free codec avoids both problems.
12
+ */
13
+ export declare function utf8Encode(text: string): Uint8Array;
14
+ export declare function utf8Decode(bytes: Uint8Array): string;
15
+ export declare function jsonToBytes(value: unknown): Uint8Array;
16
+ export declare function jsonFromBytes(buffer: ArrayBuffer): unknown;
package/lib/codec.js ADDED
@@ -0,0 +1,80 @@
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.utf8Encode = utf8Encode;
6
+ exports.utf8Decode = utf8Decode;
7
+ exports.jsonToBytes = jsonToBytes;
8
+ exports.jsonFromBytes = jsonFromBytes;
9
+ /**
10
+ * UTF-8 byte <-> string codec, shared by every JSON payload that crosses the
11
+ * JSI boundary (config, flow params, the events array, the `ContactMessage`
12
+ * JSON the pairing codec entry points exchange). Those payloads travel as
13
+ * bytes, not a JS string — the host functions read `ArrayBuffer`/typed-array
14
+ * arguments and hand back `ArrayBuffer` results (see `ProtocolHost.cpp`'s
15
+ * `bytesToArrayBuffer`). `TextDecoder`/`TextEncoder` are not declared as
16
+ * ambient globals under this package's `lib: ["ES2020"]` (no DOM lib) and the
17
+ * installed `@types/node` exposes them only from `node:util`, which does not
18
+ * exist in the Hermes runtime this code actually ships on. A small
19
+ * dependency-free codec avoids both problems.
20
+ */
21
+ function utf8Encode(text) {
22
+ const bytes = [];
23
+ for (let i = 0; i < text.length; i++) {
24
+ let code = text.charCodeAt(i);
25
+ if (code >= 0xd800 && code <= 0xdbff && i + 1 < text.length) {
26
+ const low = text.charCodeAt(i + 1);
27
+ if (low >= 0xdc00 && low <= 0xdfff) {
28
+ code = (code - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000;
29
+ i++;
30
+ }
31
+ }
32
+ if (code < 0x80) {
33
+ bytes.push(code);
34
+ }
35
+ else if (code < 0x800) {
36
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
37
+ }
38
+ else if (code < 0x10000) {
39
+ bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
40
+ }
41
+ else {
42
+ bytes.push(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 0x3f), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
43
+ }
44
+ }
45
+ return new Uint8Array(bytes);
46
+ }
47
+ function utf8Decode(bytes) {
48
+ let result = '';
49
+ let i = 0;
50
+ while (i < bytes.length) {
51
+ const byte1 = bytes[i++];
52
+ if (byte1 < 0x80) {
53
+ result += String.fromCharCode(byte1);
54
+ }
55
+ else if (byte1 < 0xe0) {
56
+ const byte2 = bytes[i++];
57
+ result += String.fromCharCode(((byte1 & 0x1f) << 6) | (byte2 & 0x3f));
58
+ }
59
+ else if (byte1 < 0xf0) {
60
+ const byte2 = bytes[i++];
61
+ const byte3 = bytes[i++];
62
+ result += String.fromCharCode(((byte1 & 0x0f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f));
63
+ }
64
+ else {
65
+ const byte2 = bytes[i++];
66
+ const byte3 = bytes[i++];
67
+ const byte4 = bytes[i++];
68
+ const code = (((byte1 & 0x07) << 18) | ((byte2 & 0x3f) << 12) | ((byte3 & 0x3f) << 6) | (byte4 & 0x3f)) -
69
+ 0x10000;
70
+ result += String.fromCharCode(0xd800 + (code >> 10), 0xdc00 + (code & 0x3ff));
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ function jsonToBytes(value) {
76
+ return utf8Encode(JSON.stringify(value));
77
+ }
78
+ function jsonFromBytes(buffer) {
79
+ return JSON.parse(utf8Decode(new Uint8Array(buffer)));
80
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Every category name `derec_error_category_name` can return, and no others.
3
+ *
4
+ * This is the authoritative list for this SDK, mirrored by a test against
5
+ * `bindings/test_fixture/enums.json` so it cannot drift from the Rust enum.
6
+ * It deliberately differs from `@derec-alliance/nodejs`'s union, which was
7
+ * the original source of this file: that union lists `"wasm"`, a category no
8
+ * C-ABI binding can ever emit, and omits `"ok"`, `"ffi"` and `"state_store"`,
9
+ * which are live arms of `library/src/interop/ffi/error_names.rs` and reachable here.
10
+ * The nodejs union is arguably wrong on its own terms too, but correcting it
11
+ * is a change to that package, not this one.
12
+ */
13
+ export declare const DEREC_ERROR_CATEGORIES: readonly ["ok", "ffi", "pairing", "sharing", "recovery", "verification", "discovery", "unpairing", "derec_message", "secret_store", "channel_store", "share_store", "input", "protobuf", "invariant", "state_store"];
14
+ export type DeRecErrorCategory = (typeof DEREC_ERROR_CATEGORIES)[number];
15
+ export interface DeRecError {
16
+ category: DeRecErrorCategory;
17
+ code: string;
18
+ message: string;
19
+ status?: number;
20
+ memo?: string;
21
+ expected?: number;
22
+ got?: number;
23
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,35 @@
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.DEREC_ERROR_CATEGORIES = void 0;
6
+ /**
7
+ * Every category name `derec_error_category_name` can return, and no others.
8
+ *
9
+ * This is the authoritative list for this SDK, mirrored by a test against
10
+ * `bindings/test_fixture/enums.json` so it cannot drift from the Rust enum.
11
+ * It deliberately differs from `@derec-alliance/nodejs`'s union, which was
12
+ * the original source of this file: that union lists `"wasm"`, a category no
13
+ * C-ABI binding can ever emit, and omits `"ok"`, `"ffi"` and `"state_store"`,
14
+ * which are live arms of `library/src/interop/ffi/error_names.rs` and reachable here.
15
+ * The nodejs union is arguably wrong on its own terms too, but correcting it
16
+ * is a change to that package, not this one.
17
+ */
18
+ exports.DEREC_ERROR_CATEGORIES = [
19
+ "ok",
20
+ "ffi",
21
+ "pairing",
22
+ "sharing",
23
+ "recovery",
24
+ "verification",
25
+ "discovery",
26
+ "unpairing",
27
+ "derec_message",
28
+ "secret_store",
29
+ "channel_store",
30
+ "share_store",
31
+ "input",
32
+ "protobuf",
33
+ "invariant",
34
+ "state_store",
35
+ ];
package/lib/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './errors';
3
+ export { primitives, envelope } from './primitives';
4
+ export { DeRecProtocol, DeRecProtocolBuilder } from './protocol';
package/lib/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Copyright (c) 2026 DeRec Alliance. All rights reserved.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.DeRecProtocolBuilder = exports.DeRecProtocol = exports.envelope = exports.primitives = void 0;
20
+ __exportStar(require("./types"), exports);
21
+ __exportStar(require("./errors"), exports);
22
+ var primitives_1 = require("./primitives");
23
+ Object.defineProperty(exports, "primitives", { enumerable: true, get: function () { return primitives_1.primitives; } });
24
+ Object.defineProperty(exports, "envelope", { enumerable: true, get: function () { return primitives_1.envelope; } });
25
+ var protocol_1 = require("./protocol");
26
+ Object.defineProperty(exports, "DeRecProtocol", { enumerable: true, get: function () { return protocol_1.DeRecProtocol; } });
27
+ Object.defineProperty(exports, "DeRecProtocolBuilder", { enumerable: true, get: function () { return protocol_1.DeRecProtocolBuilder; } });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Selects which message the JSON codec operates on.
3
+ *
4
+ * Mirrors `MessageKind` in `bindings/test_fixture/enums.json`, which
5
+ * `library/src/interop/ffi/message_json.rs` asserts against — so a message
6
+ * added in Rust cannot reach this SDK as a silently unhandled value.
7
+ */
8
+ export declare enum MessageKind {
9
+ PairRequest = 0,
10
+ PairResponse = 1,
11
+ PrePairRequest = 2,
12
+ PrePairResponse = 3,
13
+ GetSecretIdsVersionsRequest = 4,
14
+ GetSecretIdsVersionsResponse = 5,
15
+ GetShareRequest = 6,
16
+ GetShareResponse = 7,
17
+ StoreShareRequest = 8,
18
+ StoreShareResponse = 9,
19
+ UnpairRequest = 10,
20
+ UnpairResponse = 11,
21
+ VerifyShareRequest = 12,
22
+ VerifyShareResponse = 13,
23
+ TransportProtocol = 14,
24
+ CommunicationInfo = 15,
25
+ ParameterRange = 16,
26
+ CommittedDeRecShare = 17
27
+ }
28
+ /** JSON as it arrives from Rust, turned into the typed surface. */
29
+ export declare function reviveMessage(value: unknown): unknown;
30
+ /** Inverse of {@link reviveMessage}. */
31
+ export declare function plainMessage(value: unknown): unknown;
32
+ /** Protobuf wire bytes -> the typed message. */
33
+ export declare function decodeMessage<T>(kind: MessageKind, proto: Uint8Array | ArrayBuffer): T;
34
+ /** The typed message -> protobuf wire bytes. */
35
+ export declare function encodeMessage(kind: MessageKind, value: unknown): Uint8Array;
36
+ /**
37
+ * Encodes an optional message argument. The C ABI reads a zero-length buffer
38
+ * as "absent" for every optional message parameter, so `null`/`undefined`
39
+ * becomes an empty array rather than a decision made here.
40
+ */
41
+ export declare function encodeOptionalMessage(kind: MessageKind, value: unknown | null | undefined): Uint8Array;
@@ -0,0 +1,192 @@
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.MessageKind = void 0;
6
+ exports.reviveMessage = reviveMessage;
7
+ exports.plainMessage = plainMessage;
8
+ exports.decodeMessage = decodeMessage;
9
+ exports.encodeMessage = encodeMessage;
10
+ exports.encodeOptionalMessage = encodeOptionalMessage;
11
+ /**
12
+ * Message marshalling.
13
+ *
14
+ * Every `extract_*` entry point in the C ABI hands back protobuf wire bytes,
15
+ * and every `produce_*` / `process_*` that takes a message takes the same.
16
+ * Go and .NET decode those with generated protobuf bindings; this SDK has
17
+ * none, so it goes through `derec_decode_message_json` /
18
+ * `derec_encode_message_json` instead (`library/src/interop/ffi/message_json.rs`),
19
+ * which re-emits each message as the JSON mirror the WASM SDKs already
20
+ * surface. The wire format therefore stays a single Rust-side decision — this
21
+ * file only changes representation.
22
+ *
23
+ * Two representation changes are needed to reach the exact JavaScript shape
24
+ * `@derec-alliance/nodejs` exposes, where `serde-wasm-bindgen` produces it
25
+ * natively:
26
+ *
27
+ * - Wide numerics cross the JSON seam as decimal strings and are `bigint`
28
+ * in the typed surface. `serde_json` would otherwise emit them as JSON
29
+ * numbers, and `JSON.parse` silently rounds anything above 2^53 — the
30
+ * corruption only appears once a real id happens to be large.
31
+ * - Byte fields cross as arrays of decimal byte values (`serde_json` draws
32
+ * no distinction for `serde_bytes`) and are `Uint8Array` in the typed
33
+ * surface.
34
+ *
35
+ * Both conversions are keyed by field name, matching the lists in
36
+ * `message_json.rs`. A name-keyed walk is deliberate: `keep_list` is a
37
+ * genuine `number[]` and must stay one, so a structural "array of numbers is
38
+ * bytes" rule would corrupt it.
39
+ */
40
+ const codec_1 = require("./codec");
41
+ const native_1 = require("./native");
42
+ /**
43
+ * Selects which message the JSON codec operates on.
44
+ *
45
+ * Mirrors `MessageKind` in `bindings/test_fixture/enums.json`, which
46
+ * `library/src/interop/ffi/message_json.rs` asserts against — so a message
47
+ * added in Rust cannot reach this SDK as a silently unhandled value.
48
+ */
49
+ var MessageKind;
50
+ (function (MessageKind) {
51
+ MessageKind[MessageKind["PairRequest"] = 0] = "PairRequest";
52
+ MessageKind[MessageKind["PairResponse"] = 1] = "PairResponse";
53
+ MessageKind[MessageKind["PrePairRequest"] = 2] = "PrePairRequest";
54
+ MessageKind[MessageKind["PrePairResponse"] = 3] = "PrePairResponse";
55
+ MessageKind[MessageKind["GetSecretIdsVersionsRequest"] = 4] = "GetSecretIdsVersionsRequest";
56
+ MessageKind[MessageKind["GetSecretIdsVersionsResponse"] = 5] = "GetSecretIdsVersionsResponse";
57
+ MessageKind[MessageKind["GetShareRequest"] = 6] = "GetShareRequest";
58
+ MessageKind[MessageKind["GetShareResponse"] = 7] = "GetShareResponse";
59
+ MessageKind[MessageKind["StoreShareRequest"] = 8] = "StoreShareRequest";
60
+ MessageKind[MessageKind["StoreShareResponse"] = 9] = "StoreShareResponse";
61
+ MessageKind[MessageKind["UnpairRequest"] = 10] = "UnpairRequest";
62
+ MessageKind[MessageKind["UnpairResponse"] = 11] = "UnpairResponse";
63
+ MessageKind[MessageKind["VerifyShareRequest"] = 12] = "VerifyShareRequest";
64
+ MessageKind[MessageKind["VerifyShareResponse"] = 13] = "VerifyShareResponse";
65
+ MessageKind[MessageKind["TransportProtocol"] = 14] = "TransportProtocol";
66
+ MessageKind[MessageKind["CommunicationInfo"] = 15] = "CommunicationInfo";
67
+ MessageKind[MessageKind["ParameterRange"] = 16] = "ParameterRange";
68
+ MessageKind[MessageKind["CommittedDeRecShare"] = 17] = "CommittedDeRecShare";
69
+ })(MessageKind || (exports.MessageKind = MessageKind = {}));
70
+ /** Fields the codec widens to decimal strings; `bigint` on this side. */
71
+ const BIGINT_STRING_FIELDS = new Set([
72
+ 'channel_id',
73
+ 'nonce',
74
+ 'secret_id',
75
+ 'replica_id',
76
+ 'min_share_size',
77
+ 'max_share_size',
78
+ 'min_time_between_verifications',
79
+ 'max_time_between_verifications',
80
+ 'min_time_between_share_updates',
81
+ 'max_time_between_share_updates',
82
+ 'min_unresponsive_deletion_timeout',
83
+ 'max_unresponsive_deletion_timeout',
84
+ 'min_unresponsive_deactivation_timeout',
85
+ 'max_unresponsive_deactivation_timeout',
86
+ ]);
87
+ /**
88
+ * `Timestamp.seconds` is an `i64` holding a Unix seconds value. It stays a
89
+ * JSON number — no realistic timestamp approaches 2^53 — but the typed
90
+ * surface declares it `bigint`, matching `@derec-alliance/nodejs`.
91
+ */
92
+ const BIGINT_NUMBER_FIELDS = new Set(['seconds']);
93
+ /** Fields carrying `Vec<u8>`; `Uint8Array` on this side. */
94
+ const BYTE_FIELDS = new Set([
95
+ 'mlkem_ciphertext',
96
+ 'mlkem_encapsulation_key',
97
+ 'ecies_public_key',
98
+ 'contact_binding_hash',
99
+ 'bytes_value',
100
+ 'share',
101
+ 'committed_de_rec_share',
102
+ 'de_rec_share',
103
+ 'commitment',
104
+ 'hash',
105
+ ]);
106
+ /** JSON as it arrives from Rust, turned into the typed surface. */
107
+ function reviveMessage(value) {
108
+ if (Array.isArray(value)) {
109
+ return value.map(reviveMessage);
110
+ }
111
+ if (value === null || typeof value !== 'object') {
112
+ return value;
113
+ }
114
+ const out = {};
115
+ for (const [key, field] of Object.entries(value)) {
116
+ if (field === null || field === undefined) {
117
+ // A `null` from an absent `Option` reads as `undefined` in the typed
118
+ // surface, matching what `serde-wasm-bindgen` produces.
119
+ out[key] = undefined;
120
+ }
121
+ else if (BIGINT_STRING_FIELDS.has(key) && typeof field === 'string') {
122
+ out[key] = BigInt(field);
123
+ }
124
+ else if (BIGINT_NUMBER_FIELDS.has(key) && typeof field === 'number') {
125
+ out[key] = BigInt(field);
126
+ }
127
+ else if (BYTE_FIELDS.has(key) && Array.isArray(field)) {
128
+ out[key] = Uint8Array.from(field);
129
+ }
130
+ else {
131
+ out[key] = reviveMessage(field);
132
+ }
133
+ }
134
+ return out;
135
+ }
136
+ /** Inverse of {@link reviveMessage}. */
137
+ function plainMessage(value) {
138
+ if (Array.isArray(value)) {
139
+ return value.map(plainMessage);
140
+ }
141
+ if (value instanceof Uint8Array) {
142
+ return Array.from(value);
143
+ }
144
+ if (typeof value === 'bigint') {
145
+ return value.toString();
146
+ }
147
+ if (value === null || typeof value !== 'object') {
148
+ return value;
149
+ }
150
+ const out = {};
151
+ for (const [key, field] of Object.entries(value)) {
152
+ if (field === undefined) {
153
+ // Left out entirely rather than sent as `null`: serde reads a missing
154
+ // `Option` field as `None`, and an explicit `null` for a field the DTO
155
+ // does not declare optional would fail to deserialize.
156
+ continue;
157
+ }
158
+ if (BIGINT_NUMBER_FIELDS.has(key) && typeof field === 'bigint') {
159
+ out[key] = Number(field);
160
+ }
161
+ else if (field instanceof Uint8Array) {
162
+ out[key] = Array.from(field);
163
+ }
164
+ else {
165
+ out[key] = plainMessage(field);
166
+ }
167
+ }
168
+ return out;
169
+ }
170
+ function host() {
171
+ return (0, native_1.getNative)();
172
+ }
173
+ /** Protobuf wire bytes -> the typed message. */
174
+ function decodeMessage(kind, proto) {
175
+ const buffer = host().decode_message_json(kind, proto);
176
+ return reviveMessage((0, codec_1.jsonFromBytes)(buffer));
177
+ }
178
+ /** The typed message -> protobuf wire bytes. */
179
+ function encodeMessage(kind, value) {
180
+ const json = (0, codec_1.jsonToBytes)(plainMessage(value));
181
+ return new Uint8Array(host().encode_message_json(kind, json));
182
+ }
183
+ /**
184
+ * Encodes an optional message argument. The C ABI reads a zero-length buffer
185
+ * as "absent" for every optional message parameter, so `null`/`undefined`
186
+ * becomes an empty array rather than a decision made here.
187
+ */
188
+ function encodeOptionalMessage(kind, value) {
189
+ return value === null || value === undefined
190
+ ? new Uint8Array(0)
191
+ : encodeMessage(kind, value);
192
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Accessor for the JSI host object installed by the native module.
3
+ *
4
+ * The host object is installed directly into the runtime's global scope
5
+ * rather than exposed through React Native's codegen, because the data
6
+ * surface passes `ArrayBuffer`s that TurboModule specs cannot express.
7
+ */
8
+ export interface DeRecNative {
9
+ version(): string;
10
+ }
11
+ export declare function getNative(): DeRecNative;
package/lib/native.js ADDED
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNative = getNative;
4
+ /**
5
+ * Asks the native module to install the host object.
6
+ *
7
+ * iOS needs this only as a fallback: the module implements
8
+ * `RCTTurboModuleWithJSIBindings`, so the platform installs the host object
9
+ * before any JavaScript runs. Android has no equivalent hook for a module that
10
+ * is not code-generated, so the install has to be triggered from here — a
11
+ * blocking synchronous `@ReactMethod`, which is why the host object is present
12
+ * by the time this returns.
13
+ *
14
+ * Everything is behind a lazy `require` and a `try`: this module is imported by
15
+ * unit tests running under plain Node, where `react-native` cannot resolve, and
16
+ * a missing native module must still surface as the explanatory error below
17
+ * rather than a resolution failure from a dependency the caller never named.
18
+ */
19
+ function requestInstall() {
20
+ try {
21
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
22
+ const { NativeModules } = require('react-native');
23
+ NativeModules.DeRec?.install?.();
24
+ }
25
+ catch {
26
+ // Left to the caller: the check below reports the actionable message.
27
+ }
28
+ }
29
+ function getNative() {
30
+ let installed = globalThis.__DeRec;
31
+ if (installed === undefined || installed === null) {
32
+ requestInstall();
33
+ installed = globalThis.__DeRec;
34
+ }
35
+ if (installed === undefined || installed === null) {
36
+ throw new Error('DeRec native module is not installed. Rebuild the app after adding ' +
37
+ '@derec-alliance/react-native (pod install on iOS, Gradle sync on Android).');
38
+ }
39
+ return installed;
40
+ }