@push.rocks/smartrust 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.rustbridge.d.ts +16 -0
- package/dist_ts/classes.rustbridge.js +251 -26
- package/dist_ts/classes.rustbridgerequesterror.d.ts +1 -1
- package/dist_ts/classes.rustbridgerequesterror.js +1 -1
- package/dist_ts/classes.stdiotransport.d.ts +14 -0
- package/dist_ts/classes.stdiotransport.js +152 -3
- package/dist_ts/interfaces/config.d.ts +19 -1
- package/dist_ts/interfaces/ipc.d.ts +14 -0
- package/dist_ts/interfaces/transport.d.ts +4 -0
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +4 -4
- package/readme.md +80 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.rustbridge.ts +287 -28
- package/ts/classes.rustbridgerequesterror.ts +2 -0
- package/ts/classes.stdiotransport.ts +152 -2
- package/ts/interfaces/config.ts +19 -1
- package/ts/interfaces/ipc.ts +16 -0
- package/ts/interfaces/transport.ts +6 -0
- package/ts/plugins.ts +2 -1
package/ts/classes.rustbridge.ts
CHANGED
|
@@ -35,6 +35,11 @@ interface IPendingRequest {
|
|
|
35
35
|
abortHandler?: () => void;
|
|
36
36
|
streaming?: StreamingResponse<any, any>;
|
|
37
37
|
admissionReservation?: IRequestAdmissionReservation;
|
|
38
|
+
sensitive?: {
|
|
39
|
+
byteLength: number;
|
|
40
|
+
writeComplete: boolean;
|
|
41
|
+
response: IManagementResponse | null;
|
|
42
|
+
};
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
interface IRequestAdmissionReservation {
|
|
@@ -73,9 +78,16 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
73
78
|
private lifecycleGeneration = 0;
|
|
74
79
|
private activationPromise: Promise<boolean> | null = null;
|
|
75
80
|
private terminationPromise: Promise<void> | null = null;
|
|
81
|
+
private sensitiveRequestId: string | null = null;
|
|
82
|
+
private sensitiveUsedGeneration: number | null = null;
|
|
83
|
+
private regularRequestPreparations = 0;
|
|
76
84
|
|
|
77
85
|
constructor(options: IRustBridgeOptions) {
|
|
78
86
|
super();
|
|
87
|
+
if (options.terminationGracePeriodMs !== undefined
|
|
88
|
+
&& (!Number.isSafeInteger(options.terminationGracePeriodMs) || options.terminationGracePeriodMs < 0)) {
|
|
89
|
+
throw new RangeError('RustBridge terminationGracePeriodMs must be a non-negative safe integer');
|
|
90
|
+
}
|
|
79
91
|
if (options.inheritedFileDescriptors) {
|
|
80
92
|
for (const descriptor of options.inheritedFileDescriptors) {
|
|
81
93
|
if (!Number.isSafeInteger(descriptor) || descriptor < 0) {
|
|
@@ -85,6 +97,11 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
85
97
|
}
|
|
86
98
|
}
|
|
87
99
|
}
|
|
100
|
+
if (options.sensitiveByteChannel
|
|
101
|
+
&& (!Number.isSafeInteger(options.sensitiveByteChannel.maxPayloadSize)
|
|
102
|
+
|| options.sensitiveByteChannel.maxPayloadSize <= 0)) {
|
|
103
|
+
throw new RangeError('RustBridge sensitiveByteChannel maxPayloadSize must be a positive safe integer');
|
|
104
|
+
}
|
|
88
105
|
if (options.maxPendingRequestsByMethod) {
|
|
89
106
|
for (const [method, limit] of Object.entries(options.maxPendingRequestsByMethod)) {
|
|
90
107
|
if (!method || !Number.isSafeInteger(limit) || limit <= 0) {
|
|
@@ -105,6 +122,9 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
105
122
|
inheritedFileDescriptors: options.inheritedFileDescriptors
|
|
106
123
|
? [...options.inheritedFileDescriptors]
|
|
107
124
|
: undefined,
|
|
125
|
+
sensitiveByteChannel: options.sensitiveByteChannel
|
|
126
|
+
? { ...options.sensitiveByteChannel }
|
|
127
|
+
: undefined,
|
|
108
128
|
maxPendingRequestsByMethod: options.maxPendingRequestsByMethod
|
|
109
129
|
? { ...options.maxPendingRequestsByMethod }
|
|
110
130
|
: undefined,
|
|
@@ -133,7 +153,9 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
133
153
|
cliArgs: this.options.cliArgs,
|
|
134
154
|
env: this.options.env,
|
|
135
155
|
inheritedFileDescriptors: this.options.inheritedFileDescriptors,
|
|
156
|
+
sensitiveByteChannel: this.options.sensitiveByteChannel,
|
|
136
157
|
maxPayloadSize: this.options.maxPayloadSize,
|
|
158
|
+
terminationGracePeriodMs: this.options.terminationGracePeriodMs,
|
|
137
159
|
logger: this.logger,
|
|
138
160
|
});
|
|
139
161
|
});
|
|
@@ -147,9 +169,9 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
147
169
|
* @param socketOptions - Optional socket connection options (reconnect, etc.)
|
|
148
170
|
*/
|
|
149
171
|
public connect(socketPath: string, socketOptions?: ISocketConnectOptions): Promise<boolean> {
|
|
150
|
-
if (this.options.inheritedFileDescriptors?.length) {
|
|
172
|
+
if (this.options.inheritedFileDescriptors?.length || this.options.sensitiveByteChannel) {
|
|
151
173
|
return Promise.reject(
|
|
152
|
-
new Error('RustBridge inheritedFileDescriptors are available only with spawn()'),
|
|
174
|
+
new Error('RustBridge inheritedFileDescriptors and sensitiveByteChannel are available only with spawn()'),
|
|
153
175
|
);
|
|
154
176
|
}
|
|
155
177
|
return this.activate(async () => new SocketTransport({
|
|
@@ -187,6 +209,8 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
187
209
|
}
|
|
188
210
|
|
|
189
211
|
const generation = ++this.lifecycleGeneration;
|
|
212
|
+
this.sensitiveRequestId = null;
|
|
213
|
+
this.sensitiveUsedGeneration = null;
|
|
190
214
|
this.lifecycleState = 'starting';
|
|
191
215
|
|
|
192
216
|
const activationPromise = (async (): Promise<boolean> => {
|
|
@@ -449,16 +473,25 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
449
473
|
);
|
|
450
474
|
}
|
|
451
475
|
|
|
476
|
+
if (this.sensitiveRequestId !== null) {
|
|
477
|
+
throw new RustBridgeRequestError(
|
|
478
|
+
`Command '${method}' cannot overlap the sensitive byte request`,
|
|
479
|
+
{ code: 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_STATE', method },
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
452
483
|
const transport = this.transport;
|
|
453
484
|
if (!transport?.connected || !this.isRunning) {
|
|
454
485
|
throw new Error(`${this.options.binaryName} bridge is not running`);
|
|
455
486
|
}
|
|
456
487
|
|
|
457
488
|
const lifecycleGeneration = this.lifecycleGeneration;
|
|
458
|
-
|
|
489
|
+
this.regularRequestPreparations++;
|
|
490
|
+
let admissionReservation: IRequestAdmissionReservation | undefined;
|
|
459
491
|
let reservationTransferred = false;
|
|
460
492
|
|
|
461
493
|
try {
|
|
494
|
+
admissionReservation = this.reservePendingRequest(method);
|
|
462
495
|
const id = `req_${++this.requestCounter}`;
|
|
463
496
|
const request: IManagementRequest = { id, method, params };
|
|
464
497
|
const json = JSON.stringify(request);
|
|
@@ -576,12 +609,198 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
576
609
|
}
|
|
577
610
|
});
|
|
578
611
|
} finally {
|
|
612
|
+
this.regularRequestPreparations--;
|
|
579
613
|
if (!reservationTransferred) {
|
|
580
614
|
this.releasePendingRequestReservation(admissionReservation);
|
|
581
615
|
}
|
|
582
616
|
}
|
|
583
617
|
}
|
|
584
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Send one request whose raw sensitive bytes travel on the generation-owned
|
|
621
|
+
* binary pipe. JSON carries only correlation, descriptor and length metadata.
|
|
622
|
+
*
|
|
623
|
+
* The supplied mutable view is exclusively owned by this call until it
|
|
624
|
+
* settles. Smartrust clears that exact view after the local write callback or
|
|
625
|
+
* failure; it cannot promise erasure of aliases, runtime or kernel copies, or
|
|
626
|
+
* receiver-owned memory.
|
|
627
|
+
*/
|
|
628
|
+
public async sendCommandWithSensitiveBytes<K extends string & keyof TCommands>(
|
|
629
|
+
method: K,
|
|
630
|
+
params: TCommands[K]['params'],
|
|
631
|
+
bytes: Uint8Array<ArrayBuffer>,
|
|
632
|
+
options?: IRustBridgeRequestOptions,
|
|
633
|
+
): Promise<TCommands[K]['result']> {
|
|
634
|
+
let cleared = false;
|
|
635
|
+
let admitted = false;
|
|
636
|
+
let reservationTransferred = false;
|
|
637
|
+
let admissionReservation: IRequestAdmissionReservation | undefined;
|
|
638
|
+
let ownedRequestId: string | null = null;
|
|
639
|
+
let ownedBytes: Uint8Array<ArrayBuffer> | null = null;
|
|
640
|
+
const clear = (): void => {
|
|
641
|
+
if (!cleared && ownedBytes) {
|
|
642
|
+
Uint8Array.prototype.fill.call(ownedBytes, 0);
|
|
643
|
+
cleared = true;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
const timeoutMs = options?.timeoutMs ?? this.options.requestTimeoutMs;
|
|
649
|
+
if (options?.timeoutMs !== undefined
|
|
650
|
+
&& (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
|
|
651
|
+
throw new RangeError('RustBridge request timeoutMs must be a positive safe integer');
|
|
652
|
+
}
|
|
653
|
+
if (!(bytes instanceof Uint8Array) || !(bytes.buffer instanceof ArrayBuffer)) {
|
|
654
|
+
throw new TypeError('RustBridge sensitive bytes must use an ArrayBuffer-backed Uint8Array');
|
|
655
|
+
}
|
|
656
|
+
ownedBytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
657
|
+
const maxPayloadSize = this.options.sensitiveByteChannel?.maxPayloadSize;
|
|
658
|
+
if (maxPayloadSize === undefined) {
|
|
659
|
+
throw new RustBridgeRequestError(
|
|
660
|
+
`Command '${method}' requires sensitiveByteChannel`,
|
|
661
|
+
{ code: 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_STATE', method },
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (ownedBytes.byteLength > maxPayloadSize) {
|
|
665
|
+
throw new RangeError(
|
|
666
|
+
`Sensitive byte payload exceeds maxPayloadSize (${ownedBytes.byteLength} > ${maxPayloadSize})`,
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
if (options?.signal?.aborted) {
|
|
670
|
+
throw new RustBridgeRequestError(
|
|
671
|
+
`Command '${method}' was aborted before it was sent`,
|
|
672
|
+
{ code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED', method, cause: options.signal.reason },
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const transport = this.transport;
|
|
677
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
678
|
+
if (!transport?.connected || !this.isRunning) {
|
|
679
|
+
throw new Error(`${this.options.binaryName} bridge is not running`);
|
|
680
|
+
}
|
|
681
|
+
if (!transport.writeSensitiveFrame || !Number.isSafeInteger(transport.sensitiveFileDescriptor)
|
|
682
|
+
|| (transport.sensitiveFileDescriptor as number) < 3) {
|
|
683
|
+
throw new RustBridgeRequestError(
|
|
684
|
+
`Command '${method}' has no owned sensitive byte pipe`,
|
|
685
|
+
{ code: 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_STATE', method },
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (this.sensitiveRequestId !== null || this.sensitiveUsedGeneration === lifecycleGeneration
|
|
689
|
+
|| this.pendingRequests.size !== 0 || this.regularRequestPreparations !== 0) {
|
|
690
|
+
throw new RustBridgeRequestError(
|
|
691
|
+
`Command '${method}' cannot acquire the one-shot sensitive byte pipe`,
|
|
692
|
+
{ code: 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_STATE', method },
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
admissionReservation = this.reservePendingRequest(method);
|
|
697
|
+
const id = `req_${++this.requestCounter}`;
|
|
698
|
+
ownedRequestId = id;
|
|
699
|
+
this.sensitiveRequestId = id;
|
|
700
|
+
this.sensitiveUsedGeneration = lifecycleGeneration;
|
|
701
|
+
admitted = true;
|
|
702
|
+
const request: IManagementRequest = {
|
|
703
|
+
id,
|
|
704
|
+
method,
|
|
705
|
+
params,
|
|
706
|
+
sensitive: {
|
|
707
|
+
protocol: 'smartrust-sensitive-v1',
|
|
708
|
+
fileDescriptor: transport.sensitiveFileDescriptor as number,
|
|
709
|
+
byteLength: ownedBytes.byteLength,
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
const json = JSON.stringify(request);
|
|
713
|
+
const byteLength = Buffer.byteLength(json, 'utf8');
|
|
714
|
+
if (byteLength > this.options.maxPayloadSize) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
`Outbound message exceeds maxPayloadSize (${byteLength} > ${this.options.maxPayloadSize})`,
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
const availabilityError = this.getRequestAvailabilityError(
|
|
720
|
+
method, options?.signal, transport, lifecycleGeneration,
|
|
721
|
+
);
|
|
722
|
+
if (availabilityError) throw availabilityError;
|
|
723
|
+
|
|
724
|
+
const result = await new Promise<TCommands[K]['result']>((resolve, reject) => {
|
|
725
|
+
const pending: IPendingRequest = {
|
|
726
|
+
resolve,
|
|
727
|
+
reject,
|
|
728
|
+
timer: null,
|
|
729
|
+
method,
|
|
730
|
+
abortSignal: options?.signal,
|
|
731
|
+
admissionReservation,
|
|
732
|
+
sensitive: { byteLength: ownedBytes!.byteLength, writeComplete: false, response: null },
|
|
733
|
+
};
|
|
734
|
+
this.pendingRequests.set(id, pending);
|
|
735
|
+
reservationTransferred = true;
|
|
736
|
+
|
|
737
|
+
if (options?.signal) {
|
|
738
|
+
pending.abortHandler = () => {
|
|
739
|
+
this.settlePendingRequest(id, {
|
|
740
|
+
outcome: 'reject',
|
|
741
|
+
error: new RustBridgeRequestError(`Command '${method}' was aborted`, {
|
|
742
|
+
code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED', requestId: id, method,
|
|
743
|
+
cause: options.signal?.reason,
|
|
744
|
+
}),
|
|
745
|
+
});
|
|
746
|
+
};
|
|
747
|
+
options.signal.addEventListener('abort', pending.abortHandler, { once: true });
|
|
748
|
+
}
|
|
749
|
+
pending.timer = setTimeout(() => {
|
|
750
|
+
this.settlePendingRequest(id, {
|
|
751
|
+
outcome: 'reject',
|
|
752
|
+
error: new RustBridgeRequestError(`Command '${method}' timed out after ${timeoutMs}ms`, {
|
|
753
|
+
code: 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT', requestId: id, method,
|
|
754
|
+
}),
|
|
755
|
+
});
|
|
756
|
+
}, timeoutMs);
|
|
757
|
+
|
|
758
|
+
void (async (): Promise<void> => {
|
|
759
|
+
try {
|
|
760
|
+
const currentAvailabilityError = this.getRequestAvailabilityError(
|
|
761
|
+
method, options?.signal, transport, lifecycleGeneration, id,
|
|
762
|
+
);
|
|
763
|
+
if (currentAvailabilityError) throw currentAvailabilityError;
|
|
764
|
+
await transport.write(json + '\n');
|
|
765
|
+
await transport.writeSensitiveFrame!(id, ownedBytes!);
|
|
766
|
+
clear();
|
|
767
|
+
this.markSensitiveWriteComplete(id);
|
|
768
|
+
} catch (error: unknown) {
|
|
769
|
+
try { clear(); } catch (clearError: unknown) {
|
|
770
|
+
error = new AggregateError([error, clearError], 'Sensitive byte write and clearing failed');
|
|
771
|
+
}
|
|
772
|
+
this.settlePendingRequest(id, {
|
|
773
|
+
outcome: 'reject',
|
|
774
|
+
error: error instanceof RustBridgeRequestError
|
|
775
|
+
? error
|
|
776
|
+
: new RustBridgeRequestError(
|
|
777
|
+
`Failed to write sensitive byte request: ${this.getErrorMessage(error)}`,
|
|
778
|
+
{ code: 'ERR_RUST_BRIDGE_REQUEST_WRITE', requestId: id, method, cause: error },
|
|
779
|
+
),
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
})();
|
|
783
|
+
});
|
|
784
|
+
return result;
|
|
785
|
+
} catch (error: unknown) {
|
|
786
|
+
if (admitted) {
|
|
787
|
+
try {
|
|
788
|
+
await this.terminate();
|
|
789
|
+
} catch (terminationError: unknown) {
|
|
790
|
+
throw new AggregateError([error, terminationError], 'Sensitive request failed and child termination was not confirmed');
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
throw error;
|
|
794
|
+
} finally {
|
|
795
|
+
try { clear(); } finally {
|
|
796
|
+
if (!reservationTransferred) this.releasePendingRequestReservation(admissionReservation);
|
|
797
|
+
if (ownedRequestId !== null && this.sensitiveRequestId === ownedRequestId) {
|
|
798
|
+
this.sensitiveRequestId = null;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
585
804
|
/**
|
|
586
805
|
* Send a streaming command to the Rust process.
|
|
587
806
|
* Returns a StreamingResponse that yields chunks via `for await...of`
|
|
@@ -593,6 +812,14 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
593
812
|
): StreamingResponse<TExtractChunk<TCommands[K]>, TCommands[K]['result']> {
|
|
594
813
|
const streaming = new StreamingResponse<TExtractChunk<TCommands[K]>, TCommands[K]['result']>();
|
|
595
814
|
|
|
815
|
+
if (this.sensitiveRequestId !== null) {
|
|
816
|
+
streaming.fail(new RustBridgeRequestError(
|
|
817
|
+
`Command '${method}' cannot overlap the sensitive byte request`,
|
|
818
|
+
{ code: 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_STATE', method },
|
|
819
|
+
));
|
|
820
|
+
return streaming;
|
|
821
|
+
}
|
|
822
|
+
|
|
596
823
|
const transport = this.transport;
|
|
597
824
|
if (!transport?.connected || !this.isRunning) {
|
|
598
825
|
streaming.fail(new Error(`${this.options.binaryName} bridge is not running`));
|
|
@@ -603,6 +830,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
603
830
|
let admissionReservation: IRequestAdmissionReservation | undefined;
|
|
604
831
|
let reservationTransferred = false;
|
|
605
832
|
let requestId: string | undefined;
|
|
833
|
+
this.regularRequestPreparations++;
|
|
606
834
|
|
|
607
835
|
try {
|
|
608
836
|
admissionReservation = this.reservePendingRequest(method);
|
|
@@ -704,6 +932,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
704
932
|
streaming.fail(error);
|
|
705
933
|
}
|
|
706
934
|
} finally {
|
|
935
|
+
this.regularRequestPreparations--;
|
|
707
936
|
if (!reservationTransferred) {
|
|
708
937
|
this.releasePendingRequestReservation(admissionReservation);
|
|
709
938
|
}
|
|
@@ -747,7 +976,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
747
976
|
|
|
748
977
|
const terminationPromise = (async (): Promise<void> => {
|
|
749
978
|
if (transport) {
|
|
750
|
-
await transport.disconnectAndWait({ gracePeriodMs: optionsArg.gracePeriodMs });
|
|
979
|
+
await transport.disconnectAndWait({ gracePeriodMs: optionsArg.gracePeriodMs ?? this.options.terminationGracePeriodMs });
|
|
751
980
|
} else if (activationPromise) {
|
|
752
981
|
// No transport exists yet (for example, binary lookup is pending). The
|
|
753
982
|
// stopping state prevents that activation from publishing a transport.
|
|
@@ -762,7 +991,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
762
991
|
|
|
763
992
|
const lateTransport = this.transport;
|
|
764
993
|
if (lateTransport && lateTransport !== transport) {
|
|
765
|
-
await lateTransport.disconnectAndWait({ gracePeriodMs: optionsArg.gracePeriodMs });
|
|
994
|
+
await lateTransport.disconnectAndWait({ gracePeriodMs: optionsArg.gracePeriodMs ?? this.options.terminationGracePeriodMs });
|
|
766
995
|
}
|
|
767
996
|
|
|
768
997
|
this.transport = null;
|
|
@@ -875,33 +1104,63 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
875
1104
|
const response = parsed as IManagementResponse;
|
|
876
1105
|
const pending = this.pendingRequests.get(response.id);
|
|
877
1106
|
if (pending) {
|
|
878
|
-
if (
|
|
879
|
-
|
|
880
|
-
outcome: 'resolve',
|
|
881
|
-
value: response.result,
|
|
882
|
-
});
|
|
883
|
-
} else {
|
|
884
|
-
const message = response.error || 'Unknown error from Rust process';
|
|
885
|
-
this.settlePendingRequest(response.id, {
|
|
886
|
-
outcome: 'reject',
|
|
887
|
-
error: new RustBridgeRequestError(
|
|
888
|
-
message,
|
|
889
|
-
{
|
|
890
|
-
code: 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE',
|
|
891
|
-
responseErrorCode:
|
|
892
|
-
typeof response.errorCode === 'string'
|
|
893
|
-
? response.errorCode
|
|
894
|
-
: undefined,
|
|
895
|
-
requestId: response.id,
|
|
896
|
-
method: pending.method,
|
|
897
|
-
},
|
|
898
|
-
),
|
|
899
|
-
});
|
|
900
|
-
}
|
|
1107
|
+
if (pending.sensitive) this.acceptSensitiveResponse(response, pending);
|
|
1108
|
+
else this.settleManagementResponse(response, pending);
|
|
901
1109
|
}
|
|
902
1110
|
}
|
|
903
1111
|
}
|
|
904
1112
|
|
|
1113
|
+
private acceptSensitiveResponse(response: IManagementResponse, pending: IPendingRequest): void {
|
|
1114
|
+
const state = pending.sensitive!;
|
|
1115
|
+
const ack = response.sensitiveAck;
|
|
1116
|
+
if (state.response !== null || typeof response.success !== 'boolean'
|
|
1117
|
+
|| !ack || typeof ack !== 'object' || Array.isArray(ack)
|
|
1118
|
+
|| Object.keys(ack).sort().join(',') !== 'byteLength,consumed,protocol'
|
|
1119
|
+
|| ack.protocol !== 'smartrust-sensitive-v1' || ack.consumed !== true
|
|
1120
|
+
|| ack.byteLength !== state.byteLength) {
|
|
1121
|
+
this.settlePendingRequest(response.id, {
|
|
1122
|
+
outcome: 'reject',
|
|
1123
|
+
error: new RustBridgeRequestError(
|
|
1124
|
+
`Command '${pending.method}' returned an invalid sensitive byte acknowledgement`,
|
|
1125
|
+
{
|
|
1126
|
+
code: 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_PROTOCOL',
|
|
1127
|
+
requestId: response.id,
|
|
1128
|
+
method: pending.method,
|
|
1129
|
+
},
|
|
1130
|
+
),
|
|
1131
|
+
});
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
state.response = response;
|
|
1135
|
+
if (state.writeComplete) this.settleManagementResponse(response, pending);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
private markSensitiveWriteComplete(id: string): void {
|
|
1139
|
+
const pending = this.pendingRequests.get(id);
|
|
1140
|
+
if (!pending?.sensitive) return;
|
|
1141
|
+
pending.sensitive.writeComplete = true;
|
|
1142
|
+
if (pending.sensitive.response) {
|
|
1143
|
+
this.settleManagementResponse(pending.sensitive.response, pending);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
private settleManagementResponse(response: IManagementResponse, pending: IPendingRequest): void {
|
|
1148
|
+
if (response.success) {
|
|
1149
|
+
this.settlePendingRequest(response.id, { outcome: 'resolve', value: response.result });
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const message = response.error || 'Unknown error from Rust process';
|
|
1153
|
+
this.settlePendingRequest(response.id, {
|
|
1154
|
+
outcome: 'reject',
|
|
1155
|
+
error: new RustBridgeRequestError(message, {
|
|
1156
|
+
code: 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE',
|
|
1157
|
+
responseErrorCode: typeof response.errorCode === 'string' ? response.errorCode : undefined,
|
|
1158
|
+
requestId: response.id,
|
|
1159
|
+
method: pending.method,
|
|
1160
|
+
}),
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
905
1164
|
private settlePendingRequest(
|
|
906
1165
|
id: string,
|
|
907
1166
|
settlement: TPendingRequestSettlement,
|
|
@@ -7,6 +7,8 @@ export type TRustBridgeRequestErrorCode =
|
|
|
7
7
|
| 'ERR_RUST_BRIDGE_REQUEST_LIMIT'
|
|
8
8
|
| 'ERR_RUST_BRIDGE_REQUEST_WRITE'
|
|
9
9
|
| 'ERR_RUST_BRIDGE_REQUEST_TRANSPORT'
|
|
10
|
+
| 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_STATE'
|
|
11
|
+
| 'ERR_RUST_BRIDGE_REQUEST_SENSITIVE_PROTOCOL'
|
|
10
12
|
| 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE';
|
|
11
13
|
|
|
12
14
|
export interface IRustBridgeRequestErrorOptions {
|