Skip to content

Commit

Permalink
refactor: remove unwanted code & improve preload plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
nomi-san committed Dec 10, 2024
1 parent 33ef538 commit 2596b07
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 183 deletions.
2 changes: 2 additions & 0 deletions plugins/src/preload/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 1,2 @@
export { rcp } from './rcp';
export { lcu } from './lcu';
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
import { rcp } from './hooks';
import { rcp } from './rcp';

interface EventData {
data: any;
Expand All @@ -10,26 10,26 @@ interface ApiListener {
(message: EventData): void;
}

let ws: WebSocket;
const eventQueue = Array<string>();
const listenersMap = new Map<string, Array<ApiListener>>();
let _ws: WebSocket;
const _eventQueue = Array<string>();
const _listenersMap = new Map<string, Array<ApiListener>>();

rcp.preInit('rcp-fe-common-libs', async function (provider) {
const { _endpoint } = provider.context.socket;
ws = new WebSocket(_endpoint, 'wamp');
ws.addEventListener('open', () => {
for (const e of eventQueue.splice(0, eventQueue.length)) {
ws.send(JSON.stringify([5, e]));
_ws = new WebSocket(_endpoint, 'wamp');
_ws.addEventListener('open', () => {
for (const e of _eventQueue.splice(0, _eventQueue.length)) {
_ws.send(JSON.stringify([5, e]));
}
});
ws.addEventListener('message', handleMessage);
window.addEventListener('beforeunload', () => ws.close());
_ws.addEventListener('message', handleMessage);
window.addEventListener('beforeunload', () => _ws.close());
});

function handleMessage(e: MessageEvent<string>) {
const [type, endpoint, data] = JSON.parse(e.data);
if (type === 8 && listenersMap.has(endpoint)) {
const listeners = listenersMap.get(endpoint)!;
if (type === 8 && _listenersMap.has(endpoint)) {
const listeners = _listenersMap.get(endpoint)!;
for (const callback of listeners) {
setTimeout(() => callback(<EventData>data), 0);
}
Expand All @@ -50,17 50,17 @@ function observe(api: string, listener: ApiListener) {
const endpoint = buildApi(api);
listener = listener.bind(self);

if (listenersMap.has(endpoint)) {
const arr = listenersMap.get(endpoint);
if (_listenersMap.has(endpoint)) {
const arr = _listenersMap.get(endpoint);
arr!.push(listener);
} else {
listenersMap.set(endpoint, [listener]);
_listenersMap.set(endpoint, [listener]);
}

if (ws?.readyState === 1) {
ws.send(JSON.stringify([5, endpoint]));
if (_ws?.readyState === 1) {
_ws.send(JSON.stringify([5, endpoint]));
} else {
eventQueue.push(endpoint);
_eventQueue.push(endpoint);
}

return {
Expand All @@ -70,20 70,20 @@ function observe(api: string, listener: ApiListener) {

function disconnect(api: string, listener: ApiListener) {
const endpoint = buildApi(api);
if (listenersMap.has(endpoint)) {
const arr = listenersMap.get(endpoint)!.filter(x => x !== listener);
if (_listenersMap.has(endpoint)) {
const arr = _listenersMap.get(endpoint)!.filter(x => x !== listener);
if (arr.length === 0) {
ws.send(JSON.stringify([6, endpoint]));
listenersMap.delete(endpoint);
_ws.send(JSON.stringify([6, endpoint]));
_listenersMap.delete(endpoint);
} else {
listenersMap.set(endpoint, arr);
_listenersMap.set(endpoint, arr);
}
return true;
}
return false;
}

export const socket = {
export const lcu = {
observe,
disconnect,
};
192 changes: 192 additions & 0 deletions plugins/src/preload/hooks/rcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 1,192 @@
type RcpAnnouceEvent = CustomEvent & {
errorHandler: () => any;
registrationHandler: (
registrar: (provider) => Promise<any>
) => Promise<any> | void;
};

type CallbackType = 'before' | 'after';

interface Callback {
(...args: any): void | Promise<void>;
}

interface PluginContainer {
impl: null | object;
state: 'preInit' | 'init' | 'postInit' | 'fulfilled';
}

type CallbackContainer = {
_count: number;
} & {
[k in CallbackType]?: Callback[]
};

const RCPE_PREF = 'riotPlugin.announce:';
const RCPE_PREF_LEN = RCPE_PREF.length;

const _plugins = new Map<string, PluginContainer>();
const _callbacks = new Map<string, CallbackContainer>();

function subscribePlugin(name: string) {
const type = `${RCPE_PREF}${name}`;
document.addEventListener(type, <any>onPluginAnnounce, {
once: true,
capture: false
});
}

function onPluginAnnounce(event: RcpAnnouceEvent) {
const name = event.type.substring(RCPE_PREF_LEN);
const handler = event.registrationHandler;

function handlerWrap(this: any, registrar: Parameters<typeof handler>[0]): ReturnType<typeof handler> {
return handler.call(this, async (provider) => {
const container: PluginContainer = { impl: null, state: 'preInit' };
_plugins.set(name, container);

await invokeCallbacks(name, 'before', provider);
container.state = 'init';

const api = await registrar(provider);
container.impl = api;
container.state = 'postInit';

await invokeCallbacks(name, 'after', api);
container.state = 'fulfilled';

return api;
});
}

Object.defineProperty(event, 'registrationHandler', {
value: handlerWrap,
});
}

async function invokeCallbacks(name: string, type: CallbackType, ...args: any[]) {
const container = _callbacks.get(name);
if (container === undefined)
return;

const callbacks = container[type];
if (callbacks === undefined)
return;

while (callbacks.length > 0) {
do {
container._count -= callbacks.length;
await Promise.allSettled(callbacks.splice(0).map(callback => callback(...args)));
} while (callbacks.length > 0);
}

if (container._count === 0) {
_callbacks.delete(name);
}
}

function addCallback(name: string, callback: Callback, type: CallbackType) {
let container = _callbacks.get(name);
if (container === undefined) {
container = {
_count: 0,
[type]: [],
};
_callbacks.set(name, container);
subscribePlugin(name);
}

let callbacks = container[type];
if (callbacks === undefined) {
callbacks = [];
container[type] = callbacks;
}

container._count ;
callbacks.push(callback);
}

function ensureName(name: string) {
name = String(name).toLowerCase();
if (!name.startsWith('rcp-')) {
return 'rcp-' name;
}
return name;
}

function preInit(name: string, callback: (provider: any) => any): boolean {
if (typeof callback !== 'function')
throw new TypeError(`${callback} is not a function`);

name = ensureName(name);
const plugin = _plugins.get(name);

if (plugin === undefined || plugin.state === 'preInit') {
addCallback(name, callback, 'before');
return true;
}

return false;
}

function postInit(name: string, callback: (api: any) => any, blocking: boolean = false) {
if (typeof callback !== 'function')
throw new TypeError(`${callback} is not a function`);

name = ensureName(name);
const plugin = _plugins.get(name);

if (plugin !== undefined && plugin.state === 'fulfilled')
return false;

addCallback(name, blocking ? callback : (api: any) => void callback(api), 'after');
return true;
}

function whenReadyOne(name: string) {
return new Promise<any>(resolve => {
if (!postInit(name, resolve)) {
const plugin = _plugins.get(name)!;
resolve(plugin.impl);
}
});
}

function whenReadyAll(names: string[]) {
return Promise.all(names.map(name => whenReadyOne(name)));
}

function whenReady(name: string): Promise<any>;
function whenReady(names: string[]): Promise<any[]>;
function whenReady(param) {
if (typeof param === 'string') {
const name = ensureName(param);
return whenReadyOne(name);
}

if (Array.isArray(param)) {
const names = param.map(ensureName);
return whenReadyAll(names);
}

throw new TypeError(`unexpected argument ${param}`);
}

function get(name: string) {
name = ensureName(name);
return _plugins.get(name)?.impl;
}

export const rcp = {
preInit,
postInit,
whenReady,
get,
};

Object.defineProperty(window, 'rcp', {
value: rcp,
enumerable: false,
configurable: false,
writable: false
});
36 changes: 18 additions & 18 deletions plugins/src/preload/loader.ts
Original file line number Diff line number Diff line change
@@ -1,38 1,38 @@
import { rcp, socket } from './rcp';
import { rcp, lcu } from './hooks';

const plugins = window.Pengu.plugins
const plugins = window.Pengu.plugins;

if ('disabledPlugins' in window.Pengu) {
const blacklist = new Set<number>
const disabled = String(window.Pengu.disabledPlugins)
delete window.Pengu.disabledPlugins
const blacklist = new Set<number>;
const disabled = String(window.Pengu.disabledPlugins);
delete window.Pengu.disabledPlugins;

for (const hash of disabled.split(',')) {
const num = window.parseInt(hash, 16)
blacklist.add(num)
const num = window.parseInt(hash, 16);
blacklist.add(num);
}

function getHash(str: string) {
const data = new TextEncoder().encode(str)
let hash = 0x811c9dc5
const data = new TextEncoder().encode(str);
let hash = 0x811c9dc5;

for (const byte of data) {
hash ^= byte
hash = (hash << 1) (hash << 4) (hash << 7) (hash << 8) (hash << 24)
hash ^= byte;
hash = (hash << 1) (hash << 4) (hash << 7) (hash << 8) (hash << 24);
}

return hash >>> 0
return hash >>> 0;
}

function isDisabled(path: string) {
path = path.toLowerCase().replace(/\\/g, '/')
return blacklist.has(getHash(path))
path = path.toLowerCase().replace(/\\/g, '/');
return blacklist.has(getHash(path));
}

for (let i = plugins.length - 1; i >= 0; --i) {
const entry = plugins[i]
const entry = plugins[i];
if (isDisabled(entry) || /^@default\//i.test(entry)) {
plugins.splice(i, 1)
plugins.splice(i, 1);
}
}
}
Expand All @@ -48,7 48,7 @@ async function loadPlugin(entry: string) {
if (typeof plugin.init === 'function') {
stage = 'initialize';
const pluginName = entry.substring(0, entry.indexOf('/'));
const initContext = { rcp, socket };
const initContext = { rcp, lcu, socket: lcu };
// If it's not top-level JS
if (pluginName) {
const meta = { name: pluginName };
Expand Down Expand Up @@ -83,4 83,4 @@ rcp.preInit('rcp-fe-common-libs', async function () {
await waitable;
});

export { }
export { };
Loading

0 comments on commit 2596b07

Please sign in to comment.