1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-31 08:43:54 +00:00

feat: start adding new batched proxies

This commit is contained in:
Andreas Coroiu
2025-10-31 13:58:51 +01:00
parent 91d37d51f1
commit 42905ed83a
3 changed files with 177 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
import { Observable } from "rxjs";
import { ProxyInfo, RpcObjectReference } from "./batching-proxies";
import { RpcRequestChannel } from "./client";
import { Command, Response } from "./protocol";
describe("Batching proxies", () => {
let channel: RpcChannel;
beforeEach(() => {
channel = new RpcChannel();
});
it("creates an object reference proxy", () => {
const reference = { referenceId: 1, objectType: "TestObject" };
const proxy = RpcObjectReference(channel, reference);
expect(proxy[ProxyInfo]).toEqual({
referenceId: 1,
objectType: "TestObject",
proxyType: "RpcObjectReference",
});
});
// Not sure what await itself should do yet
// it("should allow awaiting the proxy itself", async () => {
// const reference = {
// referenceId: 2,
// objectType: "AwaitableObject",
// };
// const proxy = RpcObjectReference(channel, reference);
// const awaited = await proxy;
// expect(awaited).toBe(proxy);
// });
it("returns a pending object reference proxy when accesing a property", async () => {
const reference = { referenceId: 1, objectType: "TestObject" };
const proxy = RpcObjectReference(channel, reference) as any;
const someProperty = proxy.someProperty;
expect(someProperty[ProxyInfo].proxyType).toBe("RpcPendingObjectReference");
expect(someProperty[ProxyInfo].commands).toEqual([
{ method: "get", propertyName: "someProperty" },
]);
});
});
class RpcChannel implements RpcRequestChannel {
outgoing: Command[] = [];
waitingResponses: Array<(response: Response) => void> = [];
sendCommand(command: Command): Promise<Response> {
return new Promise((resolve) => {
this.waitingResponses.push(resolve);
this.outgoing.push(command);
});
}
subscribeToRoot(): Observable<Response> {
throw new Error("Method not implemented.");
}
respond(response: Response) {
const resolver = this.waitingResponses.shift();
if (resolver) {
resolver(response);
}
}
}

View File

@@ -0,0 +1,98 @@
import { RpcRequestChannel } from "./client";
import { ReferenceId, PropertySymbol, BatchCommand, serializeSymbol } from "./protocol";
export type BatchingProxy<T> = {
[ProxyInfo]: T & {
proxyType: "RpcObjectReference" | "RpcPendingObjectReference";
};
};
export const ProxyInfo = Symbol("ProxyInfo");
/**
* A reference to a remote object.
*/
export type RpcObjectReference = {
referenceId: ReferenceId;
objectType?: string;
};
export function RpcObjectReference(channel: RpcRequestChannel, reference: RpcObjectReference) {
const target = () => {};
Object.defineProperty(target, "name", { value: `RpcObjectReference`, configurable: true });
(target as any)[ProxyInfo] = { ...reference, proxyType: "RpcObjectReference" };
return new Proxy(
target,
proxyHandler(target, channel, reference, []),
) as any as BatchingProxy<RpcObjectReference>;
}
/**
* A pending reference to a remote object.
*/
export type RpcPendingObjectReference = {
reference: RpcObjectReference;
commands: BatchCommand[];
};
export function RpcPendingObjectReference(
channel: RpcRequestChannel,
reference: RpcPendingObjectReference,
) {
const target = () => {};
Object.defineProperty(target, "name", {
value: `RpcPendingObjectReference(${reference.reference.objectType}.${commandsToString(reference.commands)})`,
configurable: true,
});
(target as any)[ProxyInfo] = { ...reference, proxyType: "RpcPendingObjectReference" };
return new Proxy(
target,
proxyHandler(target, channel, reference.reference, reference.commands),
) as any as BatchingProxy<RpcPendingObjectReference>;
}
function proxyHandler(
target: any,
channel: RpcRequestChannel,
reference: RpcObjectReference,
commands: BatchCommand[],
): any {
return {
get(target: any, property: string | PropertySymbol) {
if ((property as any) === ProxyInfo) {
return (target as any)[ProxyInfo];
}
if (property === "then") {
// Allow awaiting the proxy itself
return undefined;
}
return RpcPendingObjectReference(channel, {
reference,
commands:
typeof property === "string"
? [...commands, { method: "get", propertyName: property }]
: [...commands, { method: "get", propertySymbol: serializeSymbol(property) }],
});
},
};
}
function commandsToString(commands: BatchCommand[]): string {
return commands
.map((cmd) => {
if (cmd.method === "get") {
const prop = (cmd as any).propertyName ?? (cmd as any).propertySymbol;
return `get(${String(prop)})`;
} else if (cmd.method === "call") {
const prop = (cmd as any).propertyName ?? (cmd as any).propertySymbol;
return `call(${String(prop)})`;
}
return "???";
})
.join(".");
}

View File

@@ -3,6 +3,16 @@ export type ReferenceId = number;
export type PropertySymbol = keyof typeof PropertySymbolMap;
export type SerializedPropertySymbol = (typeof PropertySymbolMap)[keyof typeof PropertySymbolMap];
export type BatchCommand =
| { method: "get"; propertyName: string }
| { method: "get"; propertySymbol: SerializedPropertySymbol }
| { method: "call"; propertyName: string; args: unknown[] }
| {
method: "call";
propertySymbol: SerializedPropertySymbol;
args: unknown[];
};
export type Command =
| { method: "get"; referenceId: ReferenceId; propertyName: string }
| { method: "get"; referenceId: ReferenceId; propertySymbol: SerializedPropertySymbol }
@@ -13,6 +23,7 @@ export type Command =
propertySymbol: SerializedPropertySymbol;
args: unknown[];
}
| { method: "batch"; referenceId: ReferenceId; commands: BatchCommand[] }
| { method: "by_value"; referenceId: ReferenceId }
| { method: "release"; referenceId: ReferenceId };