Library for managing IITC and plugins.
npm install lib-iitc-manager --save
Example code to use in WebExtension. Imports the library, passes environment parameters and starts loading IITC and plugins.
import { Manager } from 'lib-iitc-manager';
const manager = new Manager({
storage: browser.storage.local,
message: (message, args) => {
console.log("Message for user:", message, args);
},
onProgress: isShow => {
console.log(isShow ? "Show progress bar" : "Hide progress bar");
},
injectPlugin: plugin => {
console.log("Injecting plugin:", plugin.name);
console.log(plugin.code);
},
onPluginsViewChanged: ({ plugins, categories, core }) => {
// Called whenever the plugin set or IITC core changes.
// plugins - merged view of all plugins (catalog + state + user overrides).
// categories - non-empty categories derived from plugins, sorted alphabetically.
// core - current IITC core Plugin object, or null if not yet downloaded.
console.log("Plugin list updated:", Object.keys(plugins).length, "plugins");
console.log("Categories:", Object.keys(categories));
console.log("IITC core version:", core?.version ?? "not downloaded");
},
});
manager.run().then();const { plugins, categories, core } = await manager.getPluginsView();
// plugins is a PluginDict - keyed by uid, each entry is a merged Plugin object.
// Fields: uid, name, category, status ('on'/'off'), user, override, code, ...
for (const [uid, plugin] of Object.entries(plugins)) {
console.log(uid, plugin.status, plugin.user ? '(user)' : '');
}
// categories is a CategoryViewDict - keyed by category name, sorted alphabetically.
// Each entry: { name: string, isNew: boolean }
// isNew is true if any plugin in that category was added within newPluginThreshold seconds.
for (const [name, cat] of Object.entries(categories)) {
console.log(name, cat.isNew ? '(new)' : '');
}
// core is the active IITC core Plugin object, or null if not yet downloaded.
// core.override is true when the user has installed a custom IITC core script.
if (core) {
console.log("IITC core version:", core.version, core.override ? "(user override)" : "");
}Pass onPluginsViewChanged in the config (see above), or poll with getPluginsView() as needed.
When gmApi is provided in the config, the library injects a GM API service script once, before
all other plugins. This script implements the Greasemonkey-compatible API
(GM_getValue, GM_setValue, GM_xmlhttpRequest, etc.) and communicates with the host app
via a message bridge.
const manager = new Manager({
storage: browser.storage.local,
gmApi: {
// JavaScript that sets up window.__iitc_gm_bridge__ in the page context.
// Must expose: send(data) and onResponse(callback).
bridgeAdapterCode: `
window.__iitc_gm_bridge__ = {
send(data) { window.postMessage({ type: 'FROM_PAGE', data }, '*'); },
onResponse(cb) {
window.addEventListener('message', e => {
if (e.data?.type === 'FROM_EXT') cb(e.data.payload);
});
},
};
`,
},
injectPlugin: plugin => { /* inject plugin.code into the page */ },
onPluginEvent: event => { /* see below */ },
});The GM API service script is only injected on pages that match the aggregated @match patterns
of all currently enabled plugins. This avoids injecting it on every page in the browser.
https://intel.ingress.com/* is always included as a baseline. Additional patterns are added
only when an enabled plugin declares a different @match. The onPluginEvent notification for
gm_api therefore fires only when the match list actually changes - not on every plugin event.
Mode 1 - inject() per page load:
const plugins = await manager.getEnabledPlugins();
// plugins['gm_api'] is always first and contains:
// .match - current aggregated @match patterns
// .code - bridge adapter + GM factory combinedMode 2 - onPluginEvent for content-script registration:
When a plugin is added, removed, or updated and the aggregated match list changes, onPluginEvent
fires a special event with uid === 'gm_api'. Use it to re-register the GM API content script:
onPluginEvent: event => {
if (event.plugins['gm_api']) {
const gmApi = event.plugins['gm_api'];
// event.event is always 'update'
// gmApi.match - new aggregated @match patterns
// gmApi.code - bridge adapter + GM factory to inject at document_start
reregisterContentScript(gmApi.match, gmApi.code);
return;
}
// handle regular plugin add/update/remove events...
},Plugins without a @match header do not contribute to the GM API scope.
https://intel.ingress.com/* is always present regardless of which plugins are enabled.
import { getUniqueId } from "lib-iitc-manager";
const uniqId = getUniqueId("tmp");The library uses two kinds of storage keys.
Global keys - shared across all channels:
| Key | Description |
|---|---|
channel |
Active update channel (release, beta, or custom) |
network_host |
Repository URLs per channel |
storage_version |
Schema version; incremented automatically during migrations |
last_check_update |
Unix timestamp of the last built-in plugin update check |
last_check_external_update |
Unix timestamp of the last user-plugin update check |
plugins_state |
{ [uid]: { status, statusChangedAt? } } - enabled/disabled state for every plugin the user has ever touched |
plugins_user |
{ [uid]: Plugin } - user-installed scripts (custom plugins and IITC core overrides) |
iitc_core_user |
Custom IITC core script uploaded by the user, or null |
Channel-specific keys - {channel} is one of release, beta, custom:
| Key | Description |
|---|---|
{channel}_iitc_core |
IITC core script downloaded from the channel server |
{channel}_iitc_version |
IITC version string for the channel |
{channel}_last_modified |
ETag / Last-Modified value from the channel's meta.json |
{channel}_plugins_catalog |
{ [uid]: Plugin } - plugin metadata fetched from the remote catalog |
{channel}_plugins_local |
{ [uid]: Plugin } - downloaded plugin code cache |
{channel}_update_check_interval |
Update check interval in seconds for IITC and built-in plugins |
All public methods and configuration properties have been renamed to follow TypeScript camelCase naming conventions. For example:
progressbar->onProgressinject_plugin->injectPluginplugins_view_changed->onPluginsViewChangednew_plugin_threshold->newPluginThreshold
Internal storage keys (e.g., plugins_user, plugins_state) remain in snake_case to maintain compatibility with existing data.
The following deprecated methods have been removed:
inject_user_script: UseinjectPlugininstead.ajaxGethelper: UsefetchDataorfetchResourceinstead.
${channel}_plugins_flat and ${channel}_categories is no longer written to storage. Plugins and categories are computed on demand from the plugin set and returned as part of PluginsView.
Per-channel state keys (release_plugins_state, beta_plugins_state, …) and per-channel user-plugin keys (release_plugins_user, …) have been replaced by a single plugins_state and plugins_user shared across all channels. The migration runs automatically on the first run() call.
getIITCCore() is no longer part of the public API. Use getPluginsView() instead - it now returns a core: Plugin | null field alongside plugins and categories.
The custom IITC core script was previously stored under per-channel keys (release_iitc_core_user, …). It is now stored in a single iitc_core_user key shared across all channels. The migration runs automatically on the first run() call.
lib-iitc-manager is licensed under GNU General Public License v3.0 (GPL-3.0). For distribution through application stores (Apple App Store, Google Play Store, and others), please refer to the COPYING.STORE file, which provides an exception for the application store distribution requirements while maintaining GPL-3.0 compliance for the source code.