vite-plugin-sharedworker
Version:
Make SharedWorker works like Remote Procedure Call easily
155 lines (150 loc) • 4.32 kB
JavaScript
;
function makeRpcPayload(id, name, args) {
return {
command: "rpc",
data: {
id,
name,
args
}
};
}
function makeBroadcastPayload(data) {
return {
command: "broadcast",
data
};
}
function makePingPayload() {
return {
command: "ping"
};
}
function parsePayload(payload) {
if (payload.command === "ping") {
return payload;
} else if (payload.command === "rpc") {
return payload;
} else if (payload.command === "broadcast") {
return payload;
} else {
return void 0;
}
}
function random(l, r) {
return l + Math.round(Math.random() * (r - l));
}
const character_table = "0123456789abcdefghijklmnopqrstuvwxyz";
function randomString(length = 8) {
return Array.apply(null, Array(length)).map(() => character_table[random(0, character_table.length - 1)]).join("");
}
function defineSharedWorker(self, fns) {
fns = fns.filter((fn) => typeof fn === "function");
const map = /* @__PURE__ */ new Map();
for (const fn of fns) {
map.set(fn.name, fn);
}
const ports = /* @__PURE__ */ new Map();
setInterval(() => {
const now = (/* @__PURE__ */ new Date()).getTime();
for (const [port, date] of ports) {
if (now - date.getTime() >= 2e3) {
ports.delete(port);
}
}
}, 1e3);
const messageCallbacks = [];
self.addEventListener("connect", (event) => {
const port = event.ports[0];
ports.set(port, /* @__PURE__ */ new Date());
port.addEventListener("message", async (event2) => {
const payload = parsePayload(event2.data);
if (payload) {
if (payload.command === "rpc") {
const fn = map.get(payload.data.name);
if (fn) {
const result = await fn.apply(event2, payload.data.args);
port.postMessage(makeRpcPayload(payload.data.id, payload.data.name, result));
} else {
console.error(`Unknown message: ${JSON.stringify(payload, null, 2)}`);
}
} else if (payload.command === "broadcast") {
await Promise.all(messageCallbacks.map((fn) => fn.apply(event2, [payload.data])));
} else if (payload.command === "ping") {
ports.set(port, /* @__PURE__ */ new Date());
}
}
});
port.start();
});
return {
ports() {
return [...ports.keys()];
},
dispatch(port, data) {
port.postMessage(makeBroadcastPayload(data));
},
addMessageListener(fn) {
messageCallbacks.push(fn);
},
broadcast(data) {
for (const port of ports.keys()) {
port.postMessage(makeBroadcastPayload(data));
}
}
};
}
function defineClientFactory(worker) {
worker.port.start();
const callbacks = /* @__PURE__ */ new Map();
const messageCallbacks = [];
worker.port.addEventListener("message", async (event) => {
const payload = parsePayload(event.data);
if (payload) {
if (payload.command === "rpc") {
const callback = callbacks.get(payload.data.id);
if (callback) {
callback(payload.data);
callbacks.delete(payload.data.id);
} else {
console.error(`Unknown message: ${JSON.stringify(payload, null, 2)}`);
}
} else if (payload.command === "broadcast") {
await Promise.all(messageCallbacks.map((fn) => fn.apply(event, [payload.data])));
}
}
});
setInterval(() => {
worker.port.postMessage(makePingPayload());
}, 500);
return {
defineFunction(name) {
return (...args) => {
const id = randomString();
const payload = makeRpcPayload(id, name, args);
return new Promise((res) => {
callbacks.set(id, (payload2) => {
res(payload2.args);
});
worker.port.postMessage(payload);
});
};
},
defineClient() {
return {
addMessageListener(fn) {
messageCallbacks.push(fn);
},
dispatch(data) {
worker.port.postMessage(makeBroadcastPayload(data));
}
};
}
};
}
exports.defineClientFactory = defineClientFactory;
exports.defineSharedWorker = defineSharedWorker;
exports.makeBroadcastPayload = makeBroadcastPayload;
exports.makePingPayload = makePingPayload;
exports.makeRpcPayload = makeRpcPayload;
exports.parsePayload = parsePayload;