Skip to content

Commit

Permalink
Merge pull request #22 from modos189/matching
Browse files Browse the repository at this point in the history
Implemented match/include/exclude-match/exclude support for all sites.
  • Loading branch information
modos189 authored Feb 10, 2023
2 parents b921793 + 9c5d187 commit d9bb191
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 27 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ const manager = new Manager({
console.log("Hide progress bar");
}
},
inject_user_script: code => {
inject_plugin: plugin => {
console.log("Code of UserScript plugin for embedding in a page:");
console.log(code);
console.log(plugin['code']);
}
});

Expand All @@ -50,4 +50,4 @@ const uniqId = getUniqId("tmp");

## License

[GPL-3.0 license](/LICENSE)
[GPL-3.0 license](/LICENSE)
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lib-iitc-manager",
"version": "1.4.4",
"version": "1.5.0",
"description": "Library for managing IITC plugins",
"main": "src/index.js",
"type": "module",
Expand Down Expand Up @@ -30,7 +30,6 @@
"prettier": "^2.7.1"
},
"dependencies": {
"eslint": "^8.13.0",
"xhr2": "^0.2.1"
}
}
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

import { Manager } from './manager.js';
import { parseMeta, ajaxGet, getUniqId, getUID, check_meta_match_pattern, wait, clearWait } from './helpers.js';
import { check_matching } from './matching.js';

export { Manager, parseMeta, ajaxGet, getUniqId, getUID, check_meta_match_pattern, wait, clearWait };
export { Manager, parseMeta, ajaxGet, getUniqId, getUID, check_meta_match_pattern, wait, clearWait, check_matching };
22 changes: 14 additions & 8 deletions src/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,33 +75,37 @@ export class Manager extends Worker {
*/
async inject() {
const storage = await this.storage.get([
this.channel + '_iitc_code',
this.channel + '_iitc_core',
this.channel + '_plugins_flat',
this.channel + '_plugins_local',
this.channel + '_plugins_user',
]);

const iitc_code = storage[this.channel + '_iitc_code'];

const iitc_core = storage[this.channel + '_iitc_core'];
const plugins_local = storage[this.channel + '_plugins_local'];
const plugins_user = storage[this.channel + '_plugins_user'];

if (iitc_code !== undefined) {
const userscripts = [];
if (iitc_core !== undefined && iitc_core['code'] !== undefined) {
const plugins_to_inject = [];

// IITC is injected first, then plugins. This is the correct order, because the initialization of IITC takes some time.
// During this time, plugins have time to be added to `window.bootPlugins` and are not started immediately.
// In addition, thanks to the injecting of plugins after IITC,
// plugins do not throw errors when attempting to access IITC, leaflet, etc. during the execution of the wrapper.
userscripts.push(iitc_code);
plugins_to_inject.push(iitc_core);
const plugins_flat = storage[this.channel + '_plugins_flat'];
for (const uid of Object.keys(plugins_flat)) {
if (plugins_flat[uid]['status'] === 'on') {
userscripts.push(plugins_flat[uid]['user'] === true ? plugins_user[uid]['code'] : plugins_local[uid]['code']);
plugins_to_inject.push(plugins_flat[uid]['user'] === true ? plugins_user[uid] : plugins_local[uid]);
}
}

await Promise.all(userscripts.map((code) => this.inject_user_script(code)));
await Promise.all(
plugins_to_inject.map((pl) => {
this.inject_user_script(pl['code']);
this.inject_plugin(pl);
})
);
}
}

Expand Down Expand Up @@ -144,6 +148,7 @@ export class Manager extends Worker {
}

this.inject_user_script(plugins_flat[uid]['user'] === true ? plugins_user[uid]['code'] : plugins_local[uid]['code']);
this.inject_plugin(plugins_flat[uid]['user'] === true ? plugins_user[uid] : plugins_local[uid]);

await this._save({
plugins_flat: plugins_flat,
Expand All @@ -159,6 +164,7 @@ export class Manager extends Worker {
plugins_local[uid]['code'] = response;

this.inject_user_script(plugins_local[uid]['code']);
this.inject_plugin(plugins_local[uid]);

await this._save({
plugins_flat: plugins_flat,
Expand Down
112 changes: 112 additions & 0 deletions src/matching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3

const CACHE = {};
const RE_URL = /(.*?):\/\/([^/]*)\/(.*)/;

/**
* Checks the URL for match/include plugin.
*
* @param {plugin} meta - Object with data from ==UserScript== header.
* @param {string} url - Page URL.
* @return {boolean}
*/
export function check_matching(meta, url) {
const match = meta.match || [];
const include = meta.include || [];
const match_exclude = meta['exclude-match'] || [];
const exclude = meta.exclude || [];

// match all if no @match or @include rule and set url === '<all_ingress>'
let ok = !match.length && !include.length && url === '<all_ingress>';
// @match
ok = ok || testMatch(url, match);
// @include
ok = ok || testInclude(url, include);
// @exclude-match
ok = ok && !testMatch(url, match_exclude);
// @exclude
ok = ok && !testInclude(url, exclude);
return ok;
}

function str2RE(str) {
const re = str.replace(/([.?/])/g, '\\$1').replace(/\*/g, '.*?');
return RegExp(`^${re}$`);
}

/**
* Test glob rules like `@include` and `@exclude`.
*/
export function testInclude(url, rules) {
return rules.some((rule) => {
const key = `re:${rule}`;
let re = CACHE[key];
if (!re) {
re = makeIncludeRegExp(rule);
CACHE[key] = re;
}
return re.test(url);
});
}

function makeIncludeRegExp(str) {
if (str.length > 1 && str[0] === '/' && str[str.length - 1] === '/') {
return RegExp(str.slice(1, -1)); // Regular-expression
}
return str2RE(str); // Wildcard
}

/**
* Test match rules like `@match` and `@exclude_match`.
*/
export function testMatch(url, rules) {
return rules.some((rule) => {
const key = `match:${rule}`;
let matcher = CACHE[key];
if (!matcher) {
matcher = makeMatchRegExp(rule);
CACHE[key] = matcher;
}
return matcher.test(url);
});
}

function makeMatchRegExp(rule) {
let test;
if (rule === '<all_urls>') test = () => true;
else {
const ruleParts = rule.match(RE_URL);
test = (url) => {
const parts = url.match(RE_URL);
return !!ruleParts && !!parts && matchScheme(ruleParts[1], parts[1]) && matchHost(ruleParts[2], parts[2]) && matchPath(ruleParts[3], parts[3]);
};
}
return { test };
}

function matchScheme(rule, data) {
// exact match
if (rule === data) return 1;
// * = http | https
if (rule === '*' && /^https?$/i.test(data)) return 1;
return 0;
}

function matchHost(rule, data) {
// * matches all
if (rule === '*') return 1;
// exact match
if (rule === data) return 1;
// *.example.com
if (/^\*\.[^*]*$/.test(rule)) {
// matches the specified domain
if (rule.slice(2) === data) return 1;
// matches subdomains
if (str2RE(rule).test(data)) return 1;
}
return 0;
}

function matchPath(rule, data) {
return str2RE(rule).test(data);
}
26 changes: 20 additions & 6 deletions src/migrations.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3

import { isSet, getUID } from './helpers.js';
import { isSet, getUID, parseMeta } from './helpers.js';

export function number_of_migrations() {
return migrates.length;
}

const migrates = [migration_0001, migration_0002, migration_0003];
const migrates = [migration_0001, migration_0002, migration_0003, migration_0004];

export async function migrate(storage) {
const storage_iitc_code = await storage.get(['release_iitc_code', 'beta_iitc_code', 'custom_iitc_code']);
const storage_plugins_flat = await storage.get([
'release_plugins_flat',
'beta_plugins_flat',
Expand All @@ -33,17 +34,17 @@ export async function migrate(storage) {
for (const migrate of migrates) {
const index = migrates.indexOf(migrate);
if (parseInt(storage_misc['storage_version']) < index + 1) {
await migrate(storage_plugins_flat, storage_plugins_user, storage_misc);
await migrate(storage_iitc_code, storage_plugins_flat, storage_plugins_user, storage_misc);
is_migrated = true;
}
}

storage_misc['storage_version'] = migrates.length;
await storage.set({ ...storage_plugins_flat, ...storage_plugins_user, ...storage_misc });
await storage.set({ ...storage_iitc_code, ...storage_plugins_flat, ...storage_plugins_user, ...storage_misc });
return is_migrated;
}

async function migration_0001(storage_plugins_flat) {
async function migration_0001(storage_iitc_code, storage_plugins_flat) {
for (let channel of Object.keys(storage_plugins_flat)) {
if (!isSet(storage_plugins_flat[channel])) continue;

Expand All @@ -59,7 +60,7 @@ async function migration_0001(storage_plugins_flat) {

async function migration_0002() {}

async function migration_0003(storage_plugins_flat, storage_plugins_user, storage_misc) {
async function migration_0003(storage_iitc_code, storage_plugins_flat, storage_plugins_user, storage_misc) {
if (['test', 'local'].includes(storage_misc.channel)) {
storage_misc.channel = 'release';
storage_misc.network_host.custom = storage_misc.network_host.local;
Expand All @@ -82,3 +83,16 @@ async function migration_0003(storage_plugins_flat, storage_plugins_user, storag
}
}
}

async function migration_0004(storage_iitc_code) {
for (let channel_iitc_code of Object.keys(storage_iitc_code)) {
const code = storage_iitc_code[channel_iitc_code];
const channel = channel_iitc_code.replace('_iitc_code', '');
delete storage_iitc_code[channel_iitc_code];

if (isSet(code)) {
storage_iitc_code[channel + 'iitc_core'] = parseMeta(code);
storage_iitc_code[channel + 'iitc_core']['code'] = code;
}
}
}
29 changes: 23 additions & 6 deletions src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { ajaxGet, clearWait, getUID, isSet, parseMeta, wait } from './helpers.js
* @property {manager.progressbar} progressbar - Function for controls the display of progress bar.
* @property {manager.inject_user_script} inject_user_script - Function for injecting UserScript code
* into the Ingress Intel window.
* @property {manager.inject_plugin} inject_plugin - Function for injecting UserScript plugin
* into the Ingress Intel window.
*/

/**
Expand Down Expand Up @@ -98,11 +100,20 @@ import { ajaxGet, clearWait, getUID, isSet, parseMeta, wait } from './helpers.js
/**
* Calls a function that injects UserScript code into the Ingress Intel window.
*
* @deprecated since version 1.5.0. Use {@link manager.inject_plugin} instead.
* @callback manager.inject_user_script
* @memberOf manager
* @param {string} code - UserScript code to run in the Ingress Intel window
*/

/**
* Calls a function that injects UserScript plugin into the Ingress Intel window.
*
* @callback manager.inject_plugin
* @memberOf manager
* @param {plugin} plugin - UserScript plugin to run in the Ingress Intel window
*/

/**
* Key-value data in storage
*
Expand All @@ -122,9 +133,12 @@ import { ajaxGet, clearWait, getUID, isSet, parseMeta, wait } from './helpers.js
* @property {string} version
* @property {string} description
* @property {string} namespace
* @property {string} match
* @property {string} include
* @property {string} grant
* @property {string[]} match
* @property {string[]} include
* @property {string[]} exclude-match
* @property {string[]} exclude
* @property {string[]} require
* @property {string[]} grant
*/

/**
Expand All @@ -146,7 +160,8 @@ export class Worker {
this.storage = typeof this.config.storage !== 'undefined' ? this.config.storage : console.error("config key 'storage' is not set");
this.message = this.config.message;
this.progressbar = this.config.progressbar;
this.inject_user_script = this.config.inject_user_script;
this.inject_user_script = this.config.inject_user_script || function () {};
this.inject_plugin = this.config.inject_plugin || function () {};

this.is_initialized = false;
this._init().then();
Expand Down Expand Up @@ -211,7 +226,7 @@ export class Worker {
async _save(options) {
const data = {};
Object.keys(options).forEach((key) => {
if (['iitc_version', 'last_modified', 'iitc_code', 'categories', 'plugins_flat', 'plugins_local', 'plugins_user'].indexOf(key) !== -1) {
if (['iitc_version', 'last_modified', 'iitc_core', 'categories', 'plugins_flat', 'plugins_local', 'plugins_user'].indexOf(key) !== -1) {
data[this.channel + '_' + key] = options[key];
} else {
data[key] = options[key];
Expand Down Expand Up @@ -342,8 +357,10 @@ export class Worker {
const p_iitc = async () => {
const iitc_code = await this._getUrl(this.network_host[this.channel] + '/total-conversion-build.user.js');
if (iitc_code) {
const iitc_core = parseMeta(iitc_code);
iitc_core['code'] = iitc_code;
await this._save({
iitc_code: iitc_code,
iitc_core: iitc_core,
});
}
};
Expand Down
3 changes: 3 additions & 0 deletions test/manager.0.base.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ describe('manage.js base integration tests', function () {
inject_user_script: function callBack(data) {
expect(data).to.include('// ==UserScript==');
},
inject_plugin: function callBack(data) {
expect(data['code']).to.include('// ==UserScript==');
},
progressbar: function callBack(is_show) {
expect(is_show).to.be.oneOf([true, false]);
},
Expand Down
3 changes: 3 additions & 0 deletions test/manager.1.build-in.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ describe('manage.js build-in plugins integration tests', function () {
inject_user_script: function callBack(data) {
expect(data).to.include('// ==UserScript==');
},
inject_plugin: function callBack(data) {
expect(data['code']).to.include('// ==UserScript==');
},
progressbar: function callBack(is_show) {
expect(is_show).to.be.oneOf([true, false]);
},
Expand Down
3 changes: 3 additions & 0 deletions test/manager.2.external.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ describe('manage.js external plugins integration tests', function () {
inject_user_script: function callBack(data) {
expect(data).to.include('// ==UserScript==');
},
inject_plugin: function callBack(data) {
expect(data['code']).to.include('// ==UserScript==');
},
progressbar: function callBack(is_show) {
expect(is_show).to.be.oneOf([true, false]);
},
Expand Down
Loading

0 comments on commit d9bb191

Please sign in to comment.