@steambrew/client
Version:
A support library for creating plugins with Millennium.
187 lines (186 loc) • 6.77 kB
JavaScript
/**
* Make reusable IPC call declarations
*
* frontend:
* ```typescript
* const method = callable<[{ arg1: string }]>("methodName"); // declare the method
* method({ arg1: 'value' }); // call the method
* ```
*
* backend:
* ```python
* def methodName(arg1: str):
* pass
* ```
*/
const callable = (_route) => (..._params) => Promise.resolve(undefined);
const m_private_context = undefined;
export const pluginSelf = m_private_context;
const CDP_PROXY_BINDING = '__millennium_cdp_proxy__';
const CDP_EXTENSION_BINDING = '__millennium_extension_route__';
const CDP_EXT_RESP = 'MILLENNIUM_CHROME_DEV_TOOLS_PROTOCOL_DO_NOT_USE_OR_OVERRIDE_ONMESSAGE';
const BindPluginSettings = () => undefined;
const pluginConfig = { get: async () => undefined, set: async () => { }, delete: async () => { }, getAll: async () => ({}) };
const usePluginConfig = (() => [undefined, async () => { }]);
const subscribePluginConfig = () => () => { };
let _nextId = 0;
const _pending = new Map();
const _eventDispatchers = new Set();
let _busInitialized = false;
function initializeCDPBus() {
if (_busInitialized)
return;
_busInitialized = true;
window.__millennium_cdp_resolve__ = (callbackId, result) => {
const pending = _pending.get(callbackId);
if (pending) {
_pending.delete(callbackId);
pending.resolve(result ?? {});
}
};
window.__millennium_cdp_reject__ = (callbackId, error) => {
const pending = _pending.get(callbackId);
if (pending) {
_pending.delete(callbackId);
pending.reject(new Error(`CDP Error: ${error}`));
}
};
window.__millennium_cdp_event__ = (data) => {
for (const cb of _eventDispatchers) {
try {
cb(data);
}
catch (_) { }
}
};
window[CDP_EXT_RESP] = {
__handleCDPResponse: (response) => {
if (response.id !== undefined) {
const pending = _pending.get(response.id);
if (pending) {
_pending.delete(response.id);
if (response.error) {
pending.reject(new Error(`CDP Error: ${response.error.message}`));
}
else {
pending.resolve(response.result ?? {});
}
}
return;
}
if (response.method !== undefined) {
for (const cb of _eventDispatchers) {
try {
cb(response);
}
catch (_) { }
}
}
},
};
}
export class MillenniumChromeDevToolsProtocol {
constructor(pluginName) {
this._pluginName = pluginName;
this.eventListeners = new Map();
const eventListeners = this.eventListeners;
this._eventDispatcher = (data) => {
if (data.method === undefined)
return;
const params = data.sessionId ? { ...data.params, sessionId: data.sessionId } : data.params;
const listeners = eventListeners.get(data.method);
if (listeners) {
for (const listener of listeners) {
try {
listener(params);
}
catch (_) { }
}
}
};
initializeCDPBus();
_eventDispatchers.add(this._eventDispatcher);
}
on(event, listener) {
const isFirst = !this.eventListeners.has(event) || this.eventListeners.get(event).size === 0;
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, new Set());
}
this.eventListeners.get(event).add(listener);
if (isFirst) {
try {
window[CDP_PROXY_BINDING](JSON.stringify({ action: 'subscribe', pluginName: this._pluginName, event }));
}
catch (_) { }
}
return () => this.off(event, listener);
}
off(event, listener) {
const listeners = this.eventListeners.get(event);
if (listeners) {
listeners.delete(listener);
if (listeners.size === 0) {
this.eventListeners.delete(event);
try {
window[CDP_PROXY_BINDING](JSON.stringify({ action: 'unsubscribe', pluginName: this._pluginName, event }));
}
catch (_) { }
}
}
}
send(method, params = {}, sessionId) {
if (method.startsWith('Extensions.')) {
return this._sendViaExtensionRoute(method, params, sessionId);
}
return new Promise((resolve, reject) => {
const callbackId = _nextId++;
_pending.set(callbackId, { resolve, reject });
const payload = { action: 'cdp_call', pluginName: this._pluginName, callbackId, method };
if (params && Object.keys(params).length > 0) {
payload.params = params;
}
if (sessionId) {
payload.sessionId = sessionId;
}
try {
window[CDP_PROXY_BINDING](JSON.stringify(payload));
}
catch (error) {
_pending.delete(callbackId);
reject(error);
}
});
}
_sendViaExtensionRoute(method, params, sessionId) {
return new Promise((resolve, reject) => {
const id = _nextId++;
_pending.set(id, { resolve, reject });
const payload = { id, method };
if (params && Object.keys(params).length > 0) {
payload.params = params;
}
if (sessionId) {
payload.sessionId = sessionId;
}
try {
window[CDP_EXTENSION_BINDING](JSON.stringify(payload));
}
catch (error) {
_pending.delete(id);
reject(error);
}
});
}
}
/* backwards compat with old callers (without requiring recompile with new @steambrew/ttc version). falls back to Millenniums internal CDP. */
class MillenniumChromeDevToolsProtocolShared extends MillenniumChromeDevToolsProtocol {
constructor() {
super('__millennium_internal__');
}
send(method, params = {}, sessionId) {
return this._sendViaExtensionRoute(method, params, sessionId);
}
}
const ChromeDevToolsProtocol = new MillenniumChromeDevToolsProtocolShared();
const Millennium = window.Millennium;
export { BindPluginSettings, callable, ChromeDevToolsProtocol, Millennium, pluginConfig, subscribePluginConfig, usePluginConfig };