Skip to content

Commit

Permalink
Working on the MVP
Browse files Browse the repository at this point in the history
  • Loading branch information
surma committed Mar 9, 2019
1 parent 9799dcb commit 4fa7e72
Show file tree
Hide file tree
Showing 11 changed files with 5,792 additions and 201 deletions.
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@
*.swp
node_modules
*.bak
dist/comlink.ts
dist/README.md
dist/package.json
dist
5,637 changes: 5,489 additions & 148 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "comlinkjs",
"name": "comlink",
"version": "3.1.1",
"description": "Comlink makes WebWorkers enjoyable",
"main": "comlink.js",
"module": "comlink.js",
"types": "comlink.d.ts",
"scripts": {
"build": "microbundle build src/comlink.ts -o dist -f esm,umd",
"test": "npm run linter && npm run unittest && npm run build",
"unittest": "karma start",
"linter": "prettier --write ./*.{js,ts,md,json} ./{docs,tests}/**/*.{js,ts,md,json}",
Expand All @@ -30,6 +31,7 @@
"karma-mocha": "1.3.0",
"karma-safari-launcher": "1.0.0",
"karma-safaritechpreview-launcher": "2.0.2",
"microbundle": "^0.11.0",
"mocha": "5.2.0",
"prettier": "1.16.1",
"typescript": "3.2.4"
Expand Down
176 changes: 171 additions & 5 deletions comlink.ts → src/comlink.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2017 Google Inc. All Rights Reserved.
* Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand All @@ -11,6 +11,173 @@
* limitations under the License.
*/

import * as Protocol from "./protocol.js";
import { AnyPtrRecord } from "dns";
export { Endpoint } from "./protocol.js";

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

export type PromisifyValue<T> = T extends Promise<any> ? T : Promise<T>;

export type PromisifyFunction<T> = T extends (...args: infer R1) => infer R2
? (...args: R1) => Promise<R2>
: unknown;

export type PromisifyConstructor<T> = T extends {
new (...args: infer R1): infer R2;
}
? { new (...args: R1): Promisify<R2> }
: unknown;

export type PromisifyObject<T> = T extends {}
? { [P in keyof T]: PromisifyValue<T[P]> }
: unknown;

export type Remote<T> = PromisifyFunction<T> &
PromisifyObject<T> &
PromisifyConstructor<T>;
export type Promisify<T> = Remote<T>;

export function expose(obj: any, ep: Protocol.Endpoint = self as any) {
ep.addEventListener("message", function f(ev: MessageEvent) {
if (!ev || !ev.data) {
return;
}
const msg = ev.data as Protocol.Message;
const parent = msg.path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
const rawValue = parent[msg.path.slice(-1)[0]];
switch (msg.type) {
case Protocol.MessageType.GET:
{
const value = toWireValue(rawValue);
value.id = msg.id;
(ev.source! as Protocol.Endpoint).postMessage(value);
}
break;
case Protocol.MessageType.SET:
{
parent[msg.path.slice(-1)[0]] = fromWireValue(msg.value);
}
break;
case Protocol.MessageType.APPLY:
{
const value = toWireValue(
rawValue.apply(parent, msg.argumentList.map(fromWireValue))
);
(ev.source! as Protocol.Endpoint).postMessage(value);
}
break;
case Protocol.MessageType.CONSTRUCT:
{
const value = new rawValue(...msg.argumentList);
const { port1, port2 } = new MessageChannel();
port1.start();
port2.start();
expose(value, port2);
(ev.source! as Protocol.Endpoint).postMessage(
{
type: Protocol.WireValueType.PROXY,
ep: port1
},
[port1]
);
}
break;
default:
console.warn("Unrecognized message", msg);
}
} as any);
}

export function wrap<T>(ep: Protocol.Endpoint): Remote<T> {
return createProxy<T>(ep) as any;
}

function createProxy<T>(ep: Protocol.Endpoint, path: string[] = []): Remote<T> {
const proxy = new Proxy(new Function(), {
get(_target, prop) {
console.log('>>', prop);
if (typeof prop === "symbol") {
throw Error("Can’t access symbol properties with Comlink");
}
if (prop === "then") {
if (path.length === 0) {
return { then: () => proxy };
}
return requestResponseMessage(ep, {
type: Protocol.MessageType.GET,
path
}).then(fromWireValue);
}
return createProxy(ep, [...path, prop.toString()]);
},
set(_target, prop, value) {
ep.postMessage({
type: Protocol.MessageType.SET,
path: [...path, prop],
value
} as Protocol.SetMessage);
// TODO(surma@): We always succeed for now. Is that good?
return true;
},
apply(_target, _thisArg, argumentList) {
return requestResponseMessage(ep, {
type: Protocol.MessageType.APPLY,
path,
argumentList: argumentList.map(toWireValue)
}).then(fromWireValue);
},
construct(_target, argumentList) {
return requestResponseMessage(ep, {
type: Protocol.MessageType.CONSTRUCT,
path,
argumentList: argumentList.map(toWireValue)
}).then(fromWireValue);
}
});
return proxy as any;
}

function toWireValue(value: any): Protocol.WireValue {
return {
type: Protocol.WireValueType.RAW,
value
};
}

function fromWireValue(value: Protocol.WireValue): any {
switch (value.type) {
case Protocol.WireValueType.RAW:
return value.value;
case Protocol.WireValueType.PROXY:
return wrap(value.endpoint);
}
}

async function requestResponseMessage(
ep: Protocol.Endpoint,
msg: Protocol.Message
): Promise<Protocol.WireValue> {
return new Promise(resolve => {
const id = generateUUID();
ep.postMessage({ id, ...msg });
ep.addEventListener("message", function l(ev: MessageEvent) {
if (!ev.data || !ev.data.uuid || ev.data.id !== id) {
return;
}
resolve(ev.data);
} as any);
});
}

function generateUUID(): string {
return new Array(4)
.fill(0)
.map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
.join("-");
}

/*
export interface Endpoint {
postMessage(message: any, transfer?: any[]): void;
addEventListener(
Expand Down Expand Up @@ -412,10 +579,8 @@ function isWindow(endpoint: Endpoint | Window): endpoint is Window {
);
}
/**
* `pingPongMessage` sends a `postMessage` and waits for a reply. Replies are
* identified by a unique id that is attached to the payload.
*/
// * `pingPongMessage` sends a `postMessage` and waits for a reply. Replies are
// * identified by a unique id that is attached to the payload.
function pingPongMessage(
endpoint: Endpoint,
msg: Object,
Expand Down Expand Up @@ -531,3 +696,4 @@ function makeInvocationResult(obj: {}): InvocationResult {
}
};
}
*/
82 changes: 82 additions & 0 deletions src/protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export interface Endpoint {
postMessage(message: any, transfer?: any[]): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: {}
): void;
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: {}
): void;
}

export const enum WireValueType {
RAW,
PROXY
}

export interface RawWireValue {
id?: string;
type: WireValueType.RAW;
value: {};
}

export interface ProxyWireValue {
id?: string;
type: WireValueType.PROXY;
endpoint: Endpoint;
}

export type WireValue = RawWireValue | ProxyWireValue;

export type MessageID = string;

export const enum MessageType {
GET,
SET,
APPLY,
CONSTRUCT
}

export interface GetMessage {
id?: MessageID;
type: MessageType.GET;
path: string[];
}

export interface SetMessage {
type: MessageType.SET;
path: string[];
value: WireValue;
}

export interface ApplyMessage {
id?: MessageID;
type: MessageType.APPLY;
path: string[];
argumentList: WireValue[];
}

export interface ConstructMessage {
id?: MessageID;
type: MessageType.CONSTRUCT;
path: string[];
argumentList: WireValue[];
}

export type Message = GetMessage | SetMessage | ApplyMessage | ConstructMessage;
2 changes: 1 addition & 1 deletion tests/fixtures/iframe.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script type="module">
import * as Comlink from "/base/dist/comlink.js";

Comlink.expose((a, b) => a + b, self.parent);
Comlink.expose((a, b) => a + b);
</script>
4 changes: 2 additions & 2 deletions tests/fixtures/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
* limitations under the License.
*/

importScripts("/base/dist/umd/comlink.js");
importScripts("/base/dist/comlink.umd.js");

Comlink.expose((a, b) => a + b, self);
Comlink.expose((a, b) => a + b);
2 changes: 1 addition & 1 deletion tests/iframe.comlink.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("Comlink across iframes", function() {
});

it("can communicate", async function() {
const proxy = Comlink.proxy(this.ifr.contentWindow);
const proxy = Comlink.wrap(this.ifr.contentWindow);
expect(await proxy(1, 3)).to.equal(4);
});
});
2 changes: 1 addition & 1 deletion tests/messagechanneladapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import * as MessageChannelAdapter from "/base/dist/messagechanneladapter.js";

describe("MessageChannelAdapter", function() {
describe.skip("MessageChannelAdapter", function() {
beforeEach(function() {
let port1, port2;
port1 = port2 = {
Expand Down
Loading

0 comments on commit 4fa7e72

Please sign in to comment.