googlechromelabs / comlink Goto Github PK
View Code? Open in Web Editor NEWComlink makes WebWorkers enjoyable.
License: Apache License 2.0
Comlink makes WebWorkers enjoyable.
License: Apache License 2.0
Wrong (outdated?) URL in the project info on GitHub: https://googlechrome.github.io/comlink/ => https://googlechromelabs.github.io/comlink/
I have found v4 release, but I'm not sure what breaking changes are. Commit log is noisy because of a ton of renovate's PRs and merge commits.
Would you provide a changelog?
It's best practice to not commit compiled outputs because they only create unneeded diffs and merge conflicts, and there's a risk of the dist files getting out of sync with the source.
WindowEndpoint smells bad. Why does it exist? Why can't you just detect that its the endpoint and manage it. As a consumer of the endpoint I should never have to care.
The current NPM distro has a couple of issues which make comlink hard to use w/ webpack et al:
import {Comlink} from 'comlinkjs'
fails in most tools.ts
source file, which makes a mess of the output file structure, as TS transpiles it, and a developer ends up with an output dir like:input:
src/
src/test.ts
node_modules/
node_modules/comlink
node_modules/comlink/comlink.ts
output
out/
out/src/index.js
out/src/index.d.ts
out/node_modules/comlink/comlink.js
out/node_modules/comlink/comlink.d.ts
Suggested fixes:
"main": "comlink.umd.js", //allows npm aware tools to find `require('comlinkjs');
"module": "comlink.es6.js", //allows ESM aware tools (webpack 2+) to do `import {Comlink} from 'comlinkjs'
"typings": "comlink.d.ts" //allows typescript to resolve the typedefs
.ts
files to NPM (this is generally recommended, as it means developers don't have to recreate the specifics of the library's tsc environment)There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.
Error type: Preset name not found within published preset config (monorepo:angularmaterial). Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.
Functions that return Promises work great with comlink, but we have a lot of APIs that use RxJS Observables. Currently these either get cloned (which is not useful) or one needs to proxy them with proxyValue()
. However, then methods like .pipe()
don't return Observable<T>
anymore, but Promise<Observable<T>>
.
How could we make it so that an Observable is not "promisified"?
Edit: see comments below for real issue (memory leak)
Objects returned by Comlink calls can't be used for further calls. Example:
<!-- index.html -->
<!doctype html>
<script src="comlink.js"></script>
<script>
"use strict";
{
const worker = new Worker("worker.js");
const api = Comlink.proxy(worker);
async function start()
{
const app = await new api.App();
const ret = await app.getObject();
await ret.log(); // error
}
start();
}
</script>
// worker.js
"use strict";
importScripts("comlink.js");
class ReturnedObject {
log()
{
console.log("ReturnedObject log() method");
}
}
class App {
getObject()
{
return new ReturnedObject();
}
}
Comlink.expose({App, ReturnedObject}, self);
In index.html, the call to await ret.log()
throws TypeError: ret.log is not a function
. ret
has become an empty object (i.e. {}
) which seems to be because it got posted raw. This appears to prevent using comlink with APIs that return other objects, which is quite a severe limit on the complexity of APIs that can be used.
I would be super interested if you figure out a way to fix this without leaking memory, since it's the same problem that is blocking via.js being usable in production.
@surma great library...
we have been playing with your library and wondering if you have ideas/plans to support exposing multiple classes/instances...
For example...
// main.js
var worker = new Worker('worker.js');
const MyClass = Comlink.proxy(worker, 'MyClass');
const instance = await new MyClass();
await instance.logSomething(); // logs “myValue = 42”
const OtherClass = Comlink.proxy(worker, 'OtherClass');
const otherInst = await new OtherClass();
await otherInst.logSomethingElse(); // logs “myOtherValue = 43”
// worker.js
const myValue = 42;
class MyClass {
logSomething() {
console.log(`myValue = ${myValue}`);
}
}
Comlink.expose(MyClass, 'MyClass', self);
class OtherClass {
logSomethingElse() {
console.log(`myOtherValue = ${myValue + 1}`);
}
}
Comlink.expose(OtherClass, 'OtherClass', self);
i played around and one simple method to implement this is to pass extra parameter string to proxy and expose method (as you can see in example above).
wondering if you have some thoughts on this use case and if this is on your roadmap?
thanks.
My project is split into two modules, one for the web worker and one for the SPA.
In my Worker I have something similar to the following code:
import * as Comlink from 'comlinkjs'
// [...] Import other stuff
export class Integration {
ping = () => Promise.resolve('pong')
// Other stuff here
}
console.log('[Worker] Starting API connection')
customEvent.init().then(() => {
console.log('[Worker] Connected to the network')
})
Comlink.expose(Integration, self)
This gets transpired and packed with Babel 7 and Webpack.
Then In my SPA instance I have
import * as Comlink from 'comlinkjs'
const workerProcess = typeof Worker !== 'undefined' ? new Worker('web-worker.js') : {}
const service = Comlink.proxy(workerProcess)
function isWorkerAlive () {
return service.ping().then((value) => value === 'pong')
}
But when I load it, I get an error:
Error: endpoint does not have all of addEventListener, removeEventListener and postMessage defined
What am I doing wrong?
In the documentation there is no explanation on how to import the module, just how to install it. it is not clear to me and I am not sure if I am missing something. Can anybody help?
Just installed comlink
via npm, I noticed there's another package comlinkjs
.
Wanted to know what package to install, I don't want to get "stuck" with an unmaintained package when you inevitably decide to focus on one.. (?)
Node doesn‘t have MessageChannel
so I need to come up with something
Just subjective opinion here, but a line like this:
// Note the usage of `await`:
const app = await new api.App();
is a little confusing to me - it makes me think that api.App
is a Promise, but then what type does the Promise resolve? I know it's a thennable that returns itself, but I think that ability of thennables probably goes over most people's heads. I think it'd be a little clearer with just a factory method:
// Note the usage of `await`:
const app = await api.createApp();
Given that a web worker is not "free" and you need to "hop" you data around it should be explained when and when not to use web workers.
Even though they are available for quite some time the adoption ratio is pretty slow (iirc). So given a little head start seems like a good idea 🤗
This library makes it almost "too easy" to use them (which is awesome) but I'm still struggling to find a good use case for them in my day to day tasks 🙈
I extended the iframe example a little, see https://gist.github.com/ssured/79471d3e78332e60567012969f716355
When running I get proper results and 2 errors:
Line 67 refers to
Line 190 in c2be4ca
Changing this line:
Line 187 in c2be4ca
to if (!event.data.id || !event.data.callPath)
fixes the problem, but I really don't know if thats OK.
Thanks for the library. Any help on getting 2 way communication done is greatly appreciated.
It would be useful to discover the classes and functions that have been exposed to the proxy.
Hello, I've been trying to use this example to make comlinkjs work in Angular: https://medium.com/lacolaco-blog/enjoyable-webworkers-in-angular-41cfeb0e6519
(example is working when checked out: example
However I can't seem to replicate the example and I always get this error in my project
ERROR Error: Uncaught (in promise): DataCloneError: Failed to execute 'postMessage' on 'Worker': Symbol(Symbol.toPrimitive) could not be cloned.
Error: Failed to execute 'postMessage' on 'Worker': Symbol(Symbol.toPrimitive) could not be cloned.
Here is my setup
//generate-data-worker.ts
import { expose } from 'comlinkjs';
export class GenerateData {
generateData(source: string) {
return new Promise((resolve, reject) => {
resolve( "TEST" );
});
}
}
expose({GenerateData}, self);
//generate-data.service.ts
import {Injectable} from '@angular/core';
import {proxy} from 'comlinkjs';
const GenerateDataWokrer = proxy<
typeof import('../worker/generate-data').GenerateData
>(
new (Worker as any)('../worker/generate-data', { type: 'module' })
);
@Injectable({
providedIn: 'root'
})
export class GenerateDataService {
async generateData(source: string): Promise<any> {
const worker = await new GenerateDataWokrer();
return await worker.generateData(source);
}
}
calling it as:
this.genDataService.generateData(``).then((value) => {
console.log(value)
});
Can you help me understand from where is the error coming from?
It would be nice to have a hosted demo people can play with.
It could be useful to have a changelog.md file to see what is changed from the previous version.
For example from 2.1 to 2.2 no idea what is the benefit to upgrade
Compiling comlink.d.ts
for a WebWorker fails with
Using tsc v2.8.3
node_modules/comlinkjs/comlink.d.ts(26,23): error TS2304: Cannot find name 'Window'.
node_modules/comlinkjs/comlink.d.ts(29,44): error TS2304: Cannot find name 'Window'.
My workaround is to remove "Window |" from method definitions for proxy
and expose
.
Dont we have another option?
It seems like comlink will only return a Promise for the last property in a property access chain:
Lines 463 to 475 in 306a9d8
Meaning a call like this should be possible:
const foo = comlink.proxy()
await foo.bar.qux()
However, the TypeScript typings currently always convert all properties to Promises:
Line 36 in 306a9d8
Meaning you have to write this:
await (await foo.bar).qux()
I need to do a lot of these nested method calls without intermediate Promise(s). How could we make them in a type safe way?
I think the other side could make the nested bar
object explicitly a proxy value with comlink.proxy()
so it is never Promisified:
class Foo {
public bar = comlink.proxy({
qux: () => 123
})
}
and comlink.proxy()
values would need to be exposed to the type system:
export interface ProxyValue {
[proxyValueSymbol]: true
}
export function proxyValue<T>(obj: T): T & ProxyValue
Then ProxifiedObject
could take that into consideration and not wrap properties into Promises that are proxied values:
declare type ProxiedObject<T> = {
[P in keyof T]: T[P] extends (...args: infer Arguments) => infer R // if is method
? (...args: Arguments) => Promisify<R> // promisify return type
: (T[P] extends ProxyValue // if it's a proxied value
? T[P] // use raw type
: Promisify<T[P]>) // regular prop, wrap in Promise
}
Which would make this work:
const foo = comlink.proxy<Foo>()
await foo.bar.qux()
we are implementing comlink for site <-> iframe communication. Sadly we still need to support IE11 which has no support for async await. Async-await transpiling with babel adds about 28kb (unminified, nogzip) to the bundle.
I see the async keyword used twice. It it ok to convert them to native promises so we don't take this perf hit?
https://github.com/GoogleChromeLabs/comlink/blob/master/comlink.ts#L178
https://github.com/GoogleChromeLabs/comlink/blob/master/comlink.ts#L211
I don't mind doing the work.
Problem: proxyValues inside of argument objects do not work.
Example:
// Here "git" is a Comlink remote object
function handleProgress (e) {
store.progress[owner + '/' + name] = e.loaded / e.total
update()
}
return git.clone({
dir,
depth: 1,
ref,
onprogress: Comlink.proxyValue(handleProgress),
url: repo
})
Expected: the handleProgress callback would be called.
Actual:
client.js:165 Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'MessagePort': function handleProgress (e) {
store.progress[owner + '/' + name] = e.loaded / e.total
update()
} could not be cloned.
Suggested Solution: Automatically wrap anything that throws a "could not be cloned" error in a ProxyValue. Then callbacks everywhere should Just Work (TM).
Would it be possible to know from a proxy, if the backing worker, or iframe is already up and will respond to your requests.
I have situation when i want wait until iframe is ready before i start calling remote api, ideally something like this
var remoteApi = await Comlink.proxy(iframe.contentWindow)
// now i am sure that iframe loaded and i can call the remote api's
Currently to do the same, i am forced to sent a postMessage from iframe when its loaded, and listen for this in main page, and then start using the remote api's using Comlink
There is a similar package called Workly. It also provides a simple API to work with a class in a WebWorker and even simpler to use.
Comlink | Workly |
---|---|
// main.js
import * as Comlink from "comlink";
const MyClass = Comlink.wrap(
new Worker("worker.js")
);
const instance = await new MyClass();
await instance.logSomething(); // worker.js
import * as Comlink from "comlink";
const myValue = 42;
class MyClass {
logSomething() {
console.log(`myValue=${myValue}`);
}
}
Comlink.expose(MyClass); |
import * as workly from "workly";
const MyClass = workly.proxy(class {
myValue = 42;
logSomething() {
console.log(`myValue=${this.myValue}`);
}
});
const instance = await new MyClass();
await instance.logSomething(); There is also an expose API in Workly but it isn't required for this example |
What are the differences and advantages of Comlink over Workly?
Hiya!
I was wondering if comlink would be able to work with WebExtensions, since in WebExtensions we have a ton of different "threads", communication because a hassle. comlink would sound like a nice solution but alas I run into some issues:
background.ts:
import {Comlink} from 'comlink';
class MyClass {
logSomething() {
console.info('hiya');
}
}
Comlink.expose(MyClass, window);
popup.ts:
import {Comlink} from 'comlink';
chrome.runtime.getBackgroundPage(async bgPage => {
if (bgPage) {
const MyClass = Comlink.proxy(bgPage);
const instance = await new (MyClass as any)();
setInterval(() => {
instance.logSomething();
}, 5000);
}
});
Now on the background page I get a total of 2 events before it crashes:
The first one goes ok and looks like this:
since the next line slices the callPath property we end up with the following error/stacktrace:
Uncaught (in promise) TypeError: Cannot read property 'slice' of undefined at comlink.es6.js:71
(anonymous) @ comlink.es6.js:71
postMessage (async)
postMessage @ comlink.es6.js:180
(anonymous) @ comlink.es6.js:105
async function (async)
(anonymous) @ comlink.es6.js:66
15:29:08.559
I was wondering if I'm doing something wrong or if WebExtensions (at least on chrome) has some implementation differences compared to other postMessage
targets?
Currently, Comlink.proxy
returns just Function
type. It is not useful because any language services or editors cannot suggest await new Foo()
interface.
My proposal is to define a type of Comlink.proxy
function, called PromisedConstructor
.
type Promised<T> = {
[P in keyof T]: T[P] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: Promise<T[P]>
};
type PromisedConstructor<T> = {
new(...args: any): Promise<Promised<T>>;
};
Usecase (w/ Angular)
import { Injectable } from '@angular/core';
import * as Comlink from 'comlinkjs';
type LoggerType = import('./logger').Logger; // import only types
type Promised<T> = {
[P in keyof T]: T[P] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: Promise<T[P]>
};
type PromisedConstructor<T> = {
new(...args: any): Promise<Promised<T>>;
};
const LoggerProxy = Comlink.proxy(
new (Worker as any)('./logger', { type: 'module' }) // global `Worker` workaround...
) as PromisedConstructor<LoggerType>;
@Injectable({
providedIn: 'root'
})
export class LoggerService {
async sendLog(value: any) {
const logger = await new LoggerProxy(); // type safe
await logger.log(value); // type-safe
}
}
I've not found out yet how to extract the original constructor's arguments type. With the above type definition, await new LoggerProxy()
's constructor arguments are all recognized as any[]
.
Thanks.
I count 4.
The MessageChannelAdapter has a leak. If it is used to transfer a MessagePort, a new event listener is added that never gets cleaned up (because smc.addEventListener
is called but smc.removeEventListener
is never called). Depending on the usage pattern, this can mean that the array of event listeners grows very quickly and has a significant performance impact on the page.
I saw the v4
branch removes the MessageChannelAdapter. Do you have a solution to this problem in mind in v4
?
I was prototyping a way to monkey-patch MessagePort#close
to send closed notifications and perform cleanup, to solve this problem. Does that sound like the right solution to you?
(Not directly related to #63, but that also talks about GCing MessagePorts.)
Using Comlink within Electron causes Electron to crash, as Electron's Node-based background process doesn't have a MessagePort
object. Comlink is referring to the MessagePort
global when enumerating the transferrable types.
If we can find some other way to test for MessagePort objects which doesn't assume the MessagePort
global exists, then Comlink could be used to communicate between Electron's background and renderer processes.
When I saw this I assumed I could slot a service worker in the same place a web worker was being used in the example, but service workers don't have self.postMessage
, so it doesn't work. I've got it working now by hooking up the proxy through a MessagePort in a message event - is that the best way to do it?
Either way, it would be great to have an example showing the best practise for this.
I'm encountering a bug that appeared after migrating from v3 to v4. It's related to babel's transpilation and proxying callback functions with Comlink.proxy
, (Comlink.proxyValue
in v3).
Some context:
I declared a transfer handler for Error
export function setTransferHandlers () {
const errorTransferHandler = {
canHandle (obj) {
return obj instanceof Error
},
serialize (obj) {
return [cloneErrorObject(obj), []]
},
deserialize (obj) {
return Object.assign(Error(), obj)
}
}
Comlink.transferHandlers.set('ERROR', errorTransferHandler)
}
Here's a block of my source code where the bug occurs.
const resolved = Promise.resolve()
function nextTickUnthrottled (fn, ...args) {
resolved.then(() => {
fn(...args)
})
}
By this point, fn
has been passed thru Comlink.proxy
. In my debugging tests, arg
is a single Error
object. After the fn
is called with the Error
arg, I expect my error transfer handler to handle it, which it does if my source code is running in the browser. That is, I expect the below func to return true.
canHandle (obj) {
return obj instanceof Error
},
However, since i'm using babel, this is the code that runs in the browser.
function nextTickUnthrottled (fn) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
resolved.then(function () {
fn.apply(void 0, args)
})
}
The important bit is fn(...args)
-> fn.apply(void 0, args)
. This results in a different code path for the proxy. Instead of the apply
trap being called (which happens with my source), it results in the get
trap being called, and THEN the apply
trap.
The bug: After upgrading to v4, the obj
param in canHandle(obj)
is a single element array with the error obj, instead of the error obj itself.
Here's a snippet of comlink's source with my comments to illustrate the differing code path:
function createProxy(ep, path = []) {
const proxy = new Proxy(new Function(), {
// BABEL CODE calls 'get' first which returns a new proxy,
// then the `apply` trap is called on the new proxy,
// but my error object has been inserted into an array
get(_target, prop) {
...
return createProxy(ep, [...path, prop]);
},
// SOURCE CODE calls 'apply' first
apply(_target, _thisArg, rawArgumentList) {
...
},
});
return proxy;
}
Let me know if you need more info.
Is this something comlink can work around?
It's been almost a year since v3.0 was released and the webpack loader still hasn't been bumped to version 3. With version 4 on the horizon, I was wondering if it would be possible to finally get some love for the webpack loader?
There is a PR open in the loader project currently GoogleChromeLabs/comlink-loader#11.
Enabling sourceMap
allows stepping into comlink code when debugging and to have stack traces that point to the real source, if the source is also included in the npm package either as files or inline in the source map.
declarationMap
in tsconfig.json allows tools to map the .d.ts files back to the source, which for example allows cross-repository go-to-definition and find-all-references on sourcegraph.com.
This is currently difficult to add because the package.json is copied into dist/
, and TypeScript would then point to ../comlink.ts
as the source, which doesn't exist because ..
would be node_modules
. Why not publish the package root? Comlink is just a single file so it's always imported through the main import anyway.
Could you please document the browser compatibility, in terms of major dependencies. E.g. is full ES2015 Proxy support required? Is there a way, when using a polyfill, to pre-enumerate known properties?
I'd love to get some best practices written up, and a quick example.
(I'm happy to do this)
Hi,
I'm not sure if I'm doing something wrong or there's a bug. What I'm doing is trying to use observable to receive the data.
Working example1:
public ngAfterViewInit(): void {
this.main().then((ifr: any) => {
const api = Comlink.proxy(ifr.contentWindow);
(api as any).CommunicationChannel.getInstance().then((_instance) => {
_instance.setUserToken('123').then(() => {
_instance.getUserToken().then(res => console.log(res));
});
});
});
}
private main(): Promise<any> {
console.log('in');
const ifr = document.querySelector('iframe');
return new Promise((resolve) => ifr.onload = () => {
resolve(ifr);
});
}
Iframe:
import {Comlink} from 'comlinkjs';
import 'rxjs/add/observable/of';
import {Observable} from 'rxjs/Observable';
/**
* Create a communication channel between the different apps
*/
export default class CommunicationChannel {
// Instance for the singleton class
private static _instance: CommunicationChannel;
// User token
private _userToken;
constructor() {
if (CommunicationChannel._instance) {
throw new Error('Error: Instantiation failed: Use SingletonClass.getInstance() instead of new.');
}
}
/**
* Create the instance
*/
public static getInstance() {
if (!CommunicationChannel._instance) {
CommunicationChannel._instance = new CommunicationChannel();
}
return Comlink.proxyValue(CommunicationChannel._instance);
}
public setUserToken(userToken: string) {
this._userToken = userToken;
}
public getUserToken(): string {
return this._userToken;
});
}
}
// Expose listens for RPC messages on endpoint and applies the operations to rootObj
Comlink.expose({CommunicationChannel}, self.parent);
Changing in observable:
public ngAfterViewInit(): void {
this.main().then((ifr: any) => {
const api = Comlink.proxy(ifr.contentWindow);
(api as any).CommunicationChannel.getInstance().then((_instance) => {
const userToken = Observable.fromPromise(_instance.getUserToken());
userToken.subscribe(res => console.log(res));
});
});
}
private main(): Promise<any> {
console.log('in');
const ifr = document.querySelector('iframe');
return new Promise((resolve) => ifr.onload = () => {
resolve(ifr);
});
}
Iframe same as before but change this method:
public getUserToken(): Observable<string> {
return new Observable((observer) => {
setInterval(() => {
observer.next('hello');
}, 1000);
});
}
Error:
comlink.es6.js:65 Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'MessagePort': function (observer) {
setInterval(function () {
observer.next('hello');
}...<omitted>... } could not be cloned.
at MessagePort.<anonymous> (http://localhost:4201/vendor.bundle.js:2588:37)
at <anonymous>
Version: 2.0.0
Demo:
index.html
<!DOCTYPE html>
<iframe src="http://127.0.0.1:2001">
</iframe>
<script src="https://cdn.jsdelivr.net/npm/comlinkjs@2/comlink.global.min.js"></script>
<script>
async function main() {
const ifr = document.querySelector('iframe');
await new Promise(resolve => ifr.onload = resolve);
const api = Comlink.proxy(ifr.contentWindow);
const instanceOfComunication = await api.Comunication.getInstance();
console.log(await instanceOfComunication.counter);
await instanceOfComunication.inc();
console.log(await instanceOfComunication.counter);
}
main();
</script>
iframe.html
<!doctype html>
<script src="https://cdn.jsdelivr.net/npm/comlinkjs@2/comlink.global.min.js"></script>
<script>
let instance = null;
class Comunication {
constructor() {
this._counter = 0;
}
static getInstance() {
if (!instance)
instance = new Comunication();
return Comlink.proxyValue(instance);
}
get counter() {
return this._counter;
}
inc() {
this._counter++;
}
}
Comlink.expose({Comunication}, self.parent);
</script>
live-server index.html --cors --port=2000
live-server iframe.html --cors --port=2001
Error:
comlink.global.min.js:1 Uncaught DOMException: Blocked a frame with origin "http://127.0.0.1:2001" from accessing a cross-origin frame.
at j (https://cdn.jsdelivr.net/npm/comlinkjs@2/comlink.global.min.js:1:1577)
at Object.c [as expose] (https://cdn.jsdelivr.net/npm/comlinkjs@2/comlink.global.min.js:1:376)
at http://127.0.0.1:2001/:24:9
j @ comlink.global.min.js:1
c @ comlink.global.min.js:1
(anonymous) @ (index):24
comlink.global.min.js:formatted:83 Uncaught (in promise) DOMException: Blocked a frame with origin "http://localhost:2000" from accessing a cross-origin frame.
at j (https://cdn.jsdelivr.net/npm/comlinkjs@2/comlink.global.min.js:1:1577)
at Object.a [as proxy] (https://cdn.jsdelivr.net/npm/comlinkjs@2/comlink.global.min.js:1:55)
at main (http://localhost:2000/:9:25)
at <anonymous>
function j(a) {
return 'Window' === a.constructor.name
}
Would you mind cutting a new release? Thanks!
I did npm i
and then npm run build
and got
> [email protected] build /home/ian/git/comlink
> rm -rf dist && mkdir dist && npm run compile && npm run mangle_global && npm run minify
> [email protected] compile /home/ian/git/comlink
> tsc --outDir dist -m none && mv dist/comlink.{,global.}js && mv dist/messagechanneladapter.{,global.}js && tsc --outDir dist -m es2015 && mv dist/comlink.{,es6.}js && mv dist/messagechanneladapter.{,es6.}js && tsc -d --outDir dist -m umd && mv dist/comlink.{,umd.}js && mv dist/messagechanneladapter.{,umd.}js
messagechanneladapter.ts(46,37): error TS2345: Argument of type '(event: MessageEvent) => void' is not assignable to parameter of type 'EventListener | EventListenerObject | undefined'.
Type '(event: MessageEvent) => void' is not assignable to type 'EventListenerObject'.
Property 'handleEvent' is missing in type '(event: MessageEvent) => void'.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] compile: `tsc --outDir dist -m none && mv dist/comlink.{,global.}js && mv dist/messagechanneladapter.{,global.}js && tsc --outDir dist -m es2015 && mv dist/comlink.{,es6.}js && mv dist/messagechanneladapter.{,es6.}js && tsc -d --outDir dist -m umd && mv dist/comlink.{,umd.}js && mv dist/messagechanneladapter.{,umd.}js`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] compile script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /home/ian/.npm/_logs/2017-11-23T14_54_50_488Z-debug.log
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] build: `rm -rf dist && mkdir dist && npm run compile && npm run mangle_global && npm run minify`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /home/ian/.npm/_logs/2017-11-23T14_54_50_508Z-debug.log
const {exportA, exportB} = Comlink.proxy(worker);
exportA
and exportB
won’t work due to how the batching proxy works. Might have to ditch it.
In example below (using master version of comlink) the call to await instance.logSomething()
in the resolved promise fails - it appears passing item through resolve calls through to comlink but gets back an incorrect object. Any ideas why this might be happening? Thanks.
// main.js
import * as Comlink from './comlink.js';
const pr = new Promise(async (resolve) => {
const items = Comlink.proxy(new Worker("worker.js", {type: "module"}));
const item2 = items['class2'];
resolve(item2);
});
pr.then(async (instance) => {
// this fails with error: TypeError: instance.logSomething is not a function
await instance.logSomething();
});
// worker.js
import { expose } from "./comlink.js";
const myValue = 42;
class MyClass {
logSomething() {
console.log(`myValue = ${myValue}`);
}
}
const exposedItems = {
class1: new MyClass(),
class2: new MyClass(),
}
expose(exposedItems, self);
Currently the document only includes "Proxy" requirement, but it caught me off-guard that async functions are also used in the code.
Proxy support: iOS >= 10.2
Async function support: iOS >= 10.3
So even if proxy is polyfilled, one must also take care of async functions support too. This incurs the inevitable configuration of babel and regenerator, which is not always desirable.
It would be great if they can be replaced with plain functions returning promises. If not then it should be documented as well in the readme, like proxy polyfill.
I'm encountering bugs that don't reproduce in the project head...
Version 4.0.0-alpha.7
dist/umd/comlink.d.ts
in the published package is broken. It imports './protocol.js'
but protocol.js
doesn't exist.
Hi All,
I just saw this amazing library and I'm trying to use it but honestly I'm struggling a bit.
On the Iframe I did (application1):
export class Comunication {
private _counter;
constructor() {
this._counter = 0;
}
get counter() {
return this._counter;
}
inc() {
debugger
this._counter++;
}
}
import {Comlink} from 'comlinkjs';
import {Comunication} from '../../common/comlink/Comunication';
export class LoginCmpstComponent {
constructor() {
Comlink.expose({Comunication}, Comlink.windowEndpoint(self.parent));
}
public login(): void {
const com: Comunication = new Comunication();
com.inc();
}
}
In the parent (application2):
public ngAfterViewInit(): void {
this.main().then(res => console.log('comLink', res));
}
public async main() {
const ifr = document.querySelector('iframe');
const api = Comlink.proxy(Comlink.windowEndpoint(ifr.contentWindow));
console.log(`${await api.prototype.counter()}`); <---- what I should put here ???
}
Can you please tell me what I should do in mine main()?
thanks
I'm not sure the best way to implement or handle this, especially since I've never used TypeScript before.
I hacked this together: https://github.com/dougmoscrop/comlink/commit/edd4004120ec6d3a155fa537f0c073b20bbc3202#diff-7c90bf97a77d0d5f32703d33f3878977R130
Hi,
I have found Comlink very useful for RPC between a React-Native app and a web app inside a WebView component.
To make it work, I had to make a few adjustments:
AddEventListener
, onMessage
that executes listeners and can be transferred to the WebView, and send
that wraps the WebView's postMessage
method.window.postMessage
and document.addEventListener
into an object that supports send
and addEventListener
. That's because of the way RN injects the postMessage
function, and since it only supports string messages.MessagePort
from the RN Comlink module, since it's undefined.I was wondering, if you think that Comlink should support this usage out of the box, or that it would be better to fork it and make the adaptations mentioned above.
Thanks!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.