mirror of
https://github.com/bitwarden/browser
synced 2026-02-16 08:34:39 +00:00
feat: fix serialization/deserialization quirks
This commit is contained in:
@@ -112,7 +112,11 @@ function RpcPropertyReference(channel: RpcRequestChannel, reference: RpcProperty
|
||||
if (result.result.type === "value") {
|
||||
return result.result.value;
|
||||
} else if (result.result.type === "reference") {
|
||||
return RpcObjectReference.create(channel, result.result.referenceId);
|
||||
return RpcObjectReference.create(
|
||||
channel,
|
||||
result.result.referenceId,
|
||||
result.result.objectType,
|
||||
);
|
||||
}
|
||||
})().then(onFulfilled, onRejected);
|
||||
};
|
||||
@@ -136,7 +140,11 @@ function RpcPropertyReference(channel: RpcRequestChannel, reference: RpcProperty
|
||||
if (result.result.type === "value") {
|
||||
return result.result.value;
|
||||
} else if (result.result.type === "reference") {
|
||||
return RpcObjectReference.create(channel, result.result.referenceId);
|
||||
return RpcObjectReference.create(
|
||||
channel,
|
||||
result.result.referenceId,
|
||||
result.result.objectType,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { firstValueFrom, map, Observable } from "rxjs";
|
||||
|
||||
import { RpcClient, RpcRequestChannel } from "./client";
|
||||
import { Response } from "./protocol";
|
||||
import { Command, Response } from "./protocol";
|
||||
import { RpcServer } from "./server";
|
||||
|
||||
describe("RpcServer", () => {
|
||||
@@ -38,6 +38,15 @@ describe("RpcServer", () => {
|
||||
|
||||
expect(result).toBe("Hello, Async World!");
|
||||
});
|
||||
|
||||
it("references Wasm-like object with pointer and free method", async () => {
|
||||
const remoteInstance = await firstValueFrom(client.getRoot());
|
||||
|
||||
const wasmObj = await remoteInstance.getWasmGreeting("Wasm World");
|
||||
const greeting = await wasmObj.greet();
|
||||
|
||||
expect(greeting).toBe("Hello, Wasm World!");
|
||||
});
|
||||
});
|
||||
|
||||
class TestClass {
|
||||
@@ -50,13 +59,38 @@ class TestClass {
|
||||
async greetAsync(name: string): Promise<string> {
|
||||
return `Hello, ${name}!`;
|
||||
}
|
||||
|
||||
getWasmGreeting(name: string): WasmLikeObject {
|
||||
return new WasmLikeObject(name);
|
||||
}
|
||||
}
|
||||
|
||||
class WasmLikeObject {
|
||||
ptr: number;
|
||||
|
||||
constructor(private name: string) {
|
||||
this.ptr = 0; // Simulated pointer
|
||||
}
|
||||
|
||||
free() {
|
||||
// Simulated free method
|
||||
}
|
||||
|
||||
async greet(): Promise<string> {
|
||||
return `Hello, ${this.name}!`;
|
||||
}
|
||||
}
|
||||
|
||||
class InMemoryChannel implements RpcRequestChannel {
|
||||
constructor(private server: RpcServer<TestClass>) {}
|
||||
|
||||
async sendCommand(command: any): Promise<any> {
|
||||
return this.server.handle(command);
|
||||
async sendCommand(command: Command): Promise<Response> {
|
||||
// Simulate serialization/deserialization
|
||||
command = JSON.parse(JSON.stringify(command));
|
||||
let response = await this.server.handle(command);
|
||||
// Simulate serialization/deserialization
|
||||
response = JSON.parse(JSON.stringify(response));
|
||||
return response;
|
||||
}
|
||||
|
||||
subscribeToRoot(): Observable<Response> {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class RpcServer<T> {
|
||||
|
||||
constructor() {}
|
||||
|
||||
handle(command: Command): Response {
|
||||
async handle(command: Command): Promise<Response> {
|
||||
if (command.method === "get") {
|
||||
const target = this.references.get<any>(command.referenceId);
|
||||
if (!target) {
|
||||
@@ -27,7 +27,7 @@ export class RpcServer<T> {
|
||||
if (typeof propertyValue === "function") {
|
||||
return { status: "error", error: `[RPC] Property ${command.propertyName} is a function` };
|
||||
} else {
|
||||
return { status: "success", result: { type: "value", value: propertyValue } };
|
||||
return { status: "success", result: this.convertToReturnable(propertyValue) };
|
||||
}
|
||||
} catch (error) {
|
||||
return { status: "error", error };
|
||||
@@ -49,8 +49,8 @@ export class RpcServer<T> {
|
||||
};
|
||||
}
|
||||
|
||||
const result = method.apply(target, command.args);
|
||||
return { status: "success", result: { type: "value", value: result } };
|
||||
const result = await method.apply(target, command.args);
|
||||
return { status: "success", result: this.convertToReturnable(result) };
|
||||
} catch (error) {
|
||||
return { status: "error", error };
|
||||
}
|
||||
@@ -62,4 +62,44 @@ export class RpcServer<T> {
|
||||
setValue(value: T) {
|
||||
this._value$.next(value);
|
||||
}
|
||||
|
||||
private convertToReturnable(value: any): Result {
|
||||
// Return a reference for objects with a 'free' method, otherwise return the value directly
|
||||
// This causes objects in WASM memory to be referenced rather than serialized.
|
||||
// TODO: Consider checking for 'ptr' instead
|
||||
if (isSerializable(value)) {
|
||||
return { type: "value", value };
|
||||
}
|
||||
|
||||
const referenceId = this.references.store(value);
|
||||
return { type: "reference", referenceId, objectType: value?.constructor?.name };
|
||||
}
|
||||
}
|
||||
|
||||
function isSerializable(value: any): boolean {
|
||||
// Primitives are serializable
|
||||
if (value === null || ["string", "number", "boolean"].includes(typeof value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Arrays are serializable if all elements are
|
||||
if (Array.isArray(value)) {
|
||||
return value.every(isSerializable);
|
||||
}
|
||||
|
||||
// Only plain objects (object literals) are serializable. Class instances should be returned by reference.
|
||||
if (isPlainObject(value)) {
|
||||
return Object.values(value).every(isSerializable);
|
||||
}
|
||||
|
||||
// Everything else (functions, dates, maps, sets, class instances, etc.) should be referenced
|
||||
return false;
|
||||
}
|
||||
|
||||
function isPlainObject(value: any): boolean {
|
||||
if (value === null || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
const proto = Object.getPrototypeOf(value);
|
||||
return proto === Object.prototype || proto === null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user