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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(".");
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user