0.1.13-stack.8 • Published 7 years ago

thin-hook v0.1.13-stack.8

Weekly downloads
3
License
BSD-2-Clause
Repository
github
Last release
7 years ago

npm version Bower version

thin-hook

Thin Hook Preprocessor (experimental)

Notes

old namenew namefeature
methodoldMethodscript.js,Class,method
cachedMethodmethodscript.js,Class,method including computed property names
  • Hook Callback Compatibility Since 0.0.149 with #123, the hook callback function has to support new operators for hooking in strict mode. See below for the updated hook callback function hook.__hook__. hook.hookCallbackCompatibilityTest() can detect if the target hook callback function is compatible or not.
  • Opaque URL Authorization Since 0.0.178 with #178, all opaque content URLs must be authorized via hook.parameters.opaque = [ 'opaque_url', ..., (url) => url.match(/opaque_url_pattern/), ... ] configuration.

Native API Access Graph generated via hook callback function (view2 of thin-hook/demo/)

Demo on GitHub Pages

Input

  class C {
    add(a = 1, b = 2) {
      let plus = (x, y) => x + y;
      return plus(a, b);
    }
  } 

Hooked Output

  class C {
    add(a, b) {
      return __hook__((a = 1, b = 2) => {
        let plus = (...args) => __hook__((x, y) => x + y, this, args, 'examples/example2.js,C,add,plus');
        return plus(a, b);
      }, this, arguments, 'examples/example2.js,C,add');
    }
  }

Preprocess

  const hook = require('thin-hook/hook.js');
  let code = fs.readFileSync('src/target.js', 'UTF-8');
  let initialContext = [['src/target.js', {}]];
  let gen = hook(code, '__hook__', initialContext, 'hash');
  fs.writeFileSync('hooked/target.js', gen);
  fs.writeFileSync('hooked/target.js.contexts.json', JSON.stringify(contexts, null, 2));

Context Generator Function (customizable)

  // Built-in Context Generator Function
  hook.contextGenerators.method = function generateMethodContext(astPath) {
    return astPath.map(([ path, node ], index) => node && node.type
      ? (node.id && node.id.name ? node.id.name : (node.key && node.key.name
        ? (node.kind === 'get' || node.kind === 'set' ? node.kind + ' ' : node.static ? 'static ' : '') + node.key.name : ''))
      : index === 0 ? path : '').filter(p => p).join(',');
  }
  // Example Custom Context Generator Function with Hashing
  const hashSalt = '__hash_salt__';
  let contexts = {};

  hook.contextGenerators.hash = function generateHashContext(astPath) {
    const hash = hook.utils.createHash('sha256');
    let hashedInitialContext = astPath[0][0];
    astPath[0][0] = contexts[hashedInitialContext] || astPath[0][0];
    let methodContext = hook.contextGenerators.method(astPath);
    astPath[0][0] = hashedInitialContext;
    hash.update(hashSalt + methodContext);
    let hashContext = hash.digest('hex');
    contexts[hashContext] = methodContext;
    return hashContext;
  }
{
  // Authorization Tickets for no-hook scripts
  // Ticket for this script itself is specified in URL of script tag as
  // hook.min.js?no-hook-authorization={ticket}
  // Note: no-hook-authorization must not exist in learning mode
  let noHookAuthorization = {
    // '*' is for learning mode to detect authorization tickets in 
    //   hook.parameters.noHookAuthorizationPassed,
    //   hook.parameters.noHookAuthorizationFailed
    // JSONs are output to console in the learning mode
    //'*': true,
    "35ae97a3305b863af7eb0ac75c8679233a2a7550e4c3046507fc9ea182c03615": true,
    "16afd3d5aa90cbd026eabcc4f09b1e4207a7042bc1e9be3b36d94415513683ed": true,
    "ae11a06c0ddec9f5b75de82a40745d6d1f92aea1459e8680171c405a5497d1c8": true,
    "5b7ebf7b0b2977d44f47ffa4b19907abbc443feb31c343a6cbbbb033c8deb01a": true,
    "c714633723320be54f106de0c50933c0aeda8ac3fba7c41c97a815ed0e71594c": true,
    "2f43d927664bdfcbcb2cc4e3743652c7eb070057efe7eaf43910426c6eae7e45": true,
    "b397e7c81cca74075d2934070cbbe58f345d3c00ff0bc04dc30b5c67715a572f": true,
    "02c107ea633ed697acc12e1b3de1bcf2f0ef7cafe4f048e29553c224656ecd7a": true,
    "aebb23ce36eb6f7d597d37727b4e6ee5a57aafc564af2d65309a9597bfd86625": true
  };
  let hidden;
  const passcode = 'XX02c107ea633ed697acc12e1b3de1bcf2f0ef7cafe4f048e29553c224656ecd7a';
  if (typeof self === 'object' && self.constructor.name === 'ServiceWorkerGlobalScope') {
    // Service Worker
    let reconfigure = false;
    if (hook.parameters.noHookAuthorization) {
      if (Object.getOwnPropertyDescriptor(hook.parameters, 'noHookAuthorization').configurable) {
        reconfigure = true;
      }
    }
    else {
      reconfigure = true;
    }
    if (reconfigure) {
      Object.defineProperty(hook.parameters, 'noHookAuthorization', {
        configurable: false,
        enumerable: true,
        get() {
          return hidden;
        },
        set(value) {
          if (value && value.passcode === passcode) {
            delete value.passcode;
            Object.freeze(value);
            hidden = value;
          }
        }
      });
    }
    noHookAuthorization.passcode = passcode;
    hook.parameters.noHookAuthorization = noHookAuthorization;
  }
  else {
    // Browser Document
    Object.defineProperty(hook.parameters, 'noHookAuthorization', {
      configurable: false,
      enumerable: true,
      writable: false,
      value: Object.freeze(noHookAuthorization)
    });
  }
  if (!noHookAuthorization['*']) {
    Object.seal(hook.parameters.noHookAuthorizationPassed);
  }
}
{
  // source map target filters
  hook.parameters.sourceMap = [
    url => location.origin === url.origin && url.pathname.match(/^\/components\/thin-hook\/demo\//)
  ];
  // hook worker script URL
  hook.parameters.hookWorker = `hook-worker.js?no-hook=true`;
}
// Hook worker script (demo/hook-worker.js)
//
// Configuration:
//   hook.parameters.hookWorker = `hook-worker.js?no-hook=true`;
importScripts('../hook.min.js?no-hook=true', 'context-generator.js?no-hook=true');
onmessage = hook.hookWorkerHandler;
  <!-- Example Custom Context Generator for Service Worker and Browser Document -->
  <script src="bower_components/thin-hook/hook.min.js?version=1&no-hook=true&hook-name=__hook__&context-generator-name=method2&fallback-page=index-fb.html&service-worker-ready=true"></script>
  <script context-generator src="custom-context-generator.js?no-hook=true"></script>
  <script context-generator no-hook>
  {
    hook.contextGenerators.method2 = function generateMethodContext2(astPath) {
      return astPath.map(([ path, node ], index) => node && node.type
        ? (node.id && node.id.name ? node.id.name : (node.key && node.key.name
          ? (node.kind === 'get' || node.kind === 'set' ? node.kind + ' ' : node.static ? 'static ' : '') + node.key.name : ''))
        : index === 0 ? path : '').filter(p => p).join(',') +
          (astPath[astPath.length - 1][1].range ? ':' + astPath[astPath.length - 1][1].range[0] + '-' + astPath[astPath.length - 1][1].range[1] : '');
    }
    Object.freeze(hook.contextGenerators);
    // CORS script list
    hook.parameters.cors = [
      'https://raw.githubusercontent.com/t2ym/thin-hook/master/examples/example1.js',
      (url) => { let _url = new URL(url); return _url.hostname !== location.hostname && ['www.gstatic.com'].indexOf(_url.hostname) < 0; }
    ];
    // Authorized opaque URL list
    hook.parameters.opaque = [
      'https://www.gstatic.com/charts/loader.js',
      (url) => {
        let _url = new URL(url);
        return _url.hostname !== location.hostname &&
          _url.href.match(/^(https:\/\/www.gstatic.com|https:\/\/apis.google.com\/js\/api.js|https:\/\/apis.google.com\/_\/)/);
      }
    ];
  }
  </script>

Hook Callback Function (customizable)

  // Built-in Minimal Hook Callback Function without hooking properties (hook-property=false)
  hook.__hook_except_properties__ = function __hook_except_properties__(f, thisArg, args, context, newTarget) {
    return newTarget
      ? Reflect.construct(f, args)
      : thisArg
        ? f.apply(thisArg, args)
        : f(...args);
  }
  // the global object
  const _global = (new Function('return this'))();

  // helper for strict mode
  class StrictModeWrapper {
    static ['#.'](o, p) { return o[p]; }
    static ['#[]'](o, p) { return o[p]; }
    static ['#*'](o) { return o; }
    static ['#in'](o, p) { return p in o; }
    static ['#()'](o, p, a) { return o[p](...a); }
    static ['#p++'](o, p) { return o[p]++; }
    static ['#++p'](o, p) { return ++o[p]; }
    static ['#p--'](o, p) { return o[p]--; }
    static ['#--p'](o, p) { return --o[p]; }
    static ['#delete'](o, p) { return delete o[p]; }
    static ['#='](o, p, v) { return o[p] = v; }
    static ['#+='](o, p, v) { return o[p] += v; }
    static ['#-='](o, p, v) { return o[p] -= v; }
    static ['#*='](o, p, v) { return o[p] *= v; }
    static ['#/='](o, p, v) { return o[p] /= v; }
    static ['#%='](o, p, v) { return o[p] %= v; }
    static ['#**='](o, p, v) { return o[p] **= v; }
    static ['#<<='](o, p, v) { return o[p] <<= v; }
    static ['#>>='](o, p, v) { return o[p] >>= v; }
    static ['#>>>='](o, p, v) { return o[p] >>>= v; }
    static ['#&='](o, p, v) { return o[p] &= v; }
    static ['#^='](o, p, v) { return o[p] ^= v; }
    static ['#|='](o, p, v) { return o[p] |= v; }
    static ['#.='](o, p) { return { set ['='](v) { o[p] = v; }, get ['=']() { return o[p]; } }; }
  }

  // Built-in Minimal Hook Callback Function with hooking properties (hook-property=true) - default
  function __hook__(f, thisArg, args, context, newTarget) {
    let normalizedThisArg = thisArg;
    if (newTarget === false) { // resolve the scope in 'with' statement body
      let varName = args[0];
      let __with__ = thisArg;
      let scope = _global;
      let _scope;
      let i;
      for (i = 0; i < __with__.length; i++) {
        _scope = __with__[i];
        if (Reflect.has(_scope, varName)) {
          if (_scope[Symbol.unscopables] && _scope[Symbol.unscopables][varName]) {
            continue;
          }
          else {
            scope = _scope;
            break;
          }
        }
      }
      thisArg = normalizedThisArg = scope;
    }
    let result;
    let args1 = args[1]; // for '()'
    function * gen() {}
    let GeneratorFunction = gen.constructor;
    switch (f) {
    case Function:
      args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args);
      break;
    case GeneratorFunction:
      args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args, true);
      break;
    case '()':
    case '#()':
      switch (thisArg) {
      case Reflect:
        switch (args[0]) {
        case 'construct':
          if (args[1]) {
            switch (args[1][0]) {
            case Function:
              args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1])];
              if (args[1][2]) {
                args1.push(args[1][2]);
              }
              break;
            default:
              if (args[1][0].prototype instanceof Function) {
                args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1], args[1][0].prototype instanceof GeneratorFunction)];
                if (args[1][2]) {
                  args1.push(args[1][2]);
                }
              }
              break;
            }
          }
          break;
        case 'apply':
          if (args[1]) {
            switch (args[1][0]) {
            case Function:
              args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2])];
              break;
            case GeneratorFunction:
              args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2], true)];
              break;
            default:
              if (args[1][0].prototype instanceof Function) {
                args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2], args[1][0].prototype instanceof GeneratorFunction)];
              }
              break;
            }
          }
          break;
        default:
          break;
        }
        break;
      case Function:
        switch (args[0]) {
        case 'apply':
          args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1])];
          break;
        case 'call':
          args1 = [args[1][0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1].slice(1))];
          break;
        default:
          break;
        }
        break;
      case GeneratorFunction:
        switch (args[0]) {
        case 'apply':
          args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1], true)];
          break;
        case 'call':
          args1 = [args[1][0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1].slice(1), true)];
          break;
        default:
          break;
        }
        break;
      default:
        if (thisArg instanceof GeneratorFunction && args[0] === 'constructor') {
          args1 = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1], true);
        }
        else if (thisArg instanceof Function && args[0] === 'constructor') {
          args1 = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1]);
        }
        break;
      }
      break;
    default:
      if (typeof f === 'function') {
        if (f.prototype instanceof Function && newTarget) {
          args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args, f.prototype instanceof GeneratorFunction);
        }
        else if (newTarget === '') {
          if (args[0] && Object.getPrototypeOf(args[0]) === Function) {
            args = [ args[0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args.slice(1)) ];
          }
        }
      }
      break;
    }
    if (typeof f !== 'string') {
      result = newTarget
        ? Reflect.construct(f, args)
        : thisArg
          ? f.apply(thisArg, args)
          : f(...args);
    }
    else {
      // property access
      switch (f) {
      // getter
      case '.':
      case '[]':
        result = thisArg[args[0]];
        break;
      // enumeration
      case '*':
        result = thisArg;
        break;
      // property existence
      case 'in':
        result = args[0] in thisArg;
        break;
      // funcation call
      case '()':
        result = thisArg[args[0]](...args1);
        break;
      // unary operators
      case 'p++':
        result = thisArg[args[0]]++;
        break;
      case '++p':
        result = ++thisArg[args[0]];
        break;
      case 'p--':
        result = thisArg[args[0]]--;
        break;
      case '--p':
        result = --thisArg[args[0]];
        break;
      case 'delete':
        result = delete thisArg[args[0]];
        break;
      // assignment operators
      case '=':
        result = thisArg[args[0]] = args[1];
        break;
      case '+=':
        result = thisArg[args[0]] += args[1];
        break;
      case '-=':
        result = thisArg[args[0]] -= args[1];
        break;
      case '*=':
        result = thisArg[args[0]] *= args[1];
        break;
      case '/=':
        result = thisArg[args[0]] /= args[1];
        break;
      case '%=':
        result = thisArg[args[0]] %= args[1];
        break;
      case '**=':
        result = thisArg[args[0]] **= args[1];
        break;
      case '<<=':
        result = thisArg[args[0]] <<= args[1];
        break;
      case '>>=':
        result = thisArg[args[0]] >>= args[1];
        break;
      case '>>>=':
        result = thisArg[args[0]] >>>= args[1];
        break;
      case '&=':
        result = thisArg[args[0]] &= args[1];
        break;
      case '^=':
        result = thisArg[args[0]] ^= args[1];
        break;
      case '|=':
        result = thisArg[args[0]] |= args[1];
        break;
      // LHS property access
      case '.=':
        result = { set ['='](v) { thisArg[args[0]] = v; }, get ['=']() { return thisArg[args[0]]; } };
        break;
      // strict mode operators prefixed with '#'
      // getter
      case '#.':
      case '#[]':
        result = StrictModeWrapper['#.'](thisArg, args[0]);
        break;
      // enumeration
      case '#*':
        result = StrictModeWrapper['#*'](thisArg);
        break;
      // property existence
      case '#in':
        result = StrictModeWrapper['#in'](thisArg, args[0]);
        break;
      // funcation call
      case '#()':
        result = StrictModeWrapper['#()'](thisArg, args[0], args1);
        break;
      // unary operators
      case '#p++':
        result = StrictModeWrapper['#p++'](thisArg, args[0]);
        break;
      case '#++p':
        result = StrictModeWrapper['#++p'](thisArg, args[0]);
        break;
      case '#p--':
        result = StrictModeWrapper['#p--'](thisArg, args[0]);
        break;
      case '#--p':
        result = StrictModeWrapper['#--p'](thisArg, args[0]);
        break;
      case '#delete':
        result = StrictModeWrapper['#delete'](thisArg, args[0]);
        break;
      // assignment operators
      case '#=':
        result = StrictModeWrapper['#='](thisArg, args[0], args[1]);
        break;
      case '#+=':
        result = StrictModeWrapper['#+='](thisArg, args[0], args[1]);
        break;
      case '#-=':
        result = StrictModeWrapper['#-='](thisArg, args[0], args[1]);
        break;
      case '#*=':
        result = StrictModeWrapper['#*='](thisArg, args[0], args[1]);
        break;
      case '#/=':
        result = StrictModeWrapper['#/='](thisArg, args[0], args[1]);
        break;
      case '#%=':
        result = StrictModeWrapper['#%='](thisArg, args[0], args[1]);
        break;
      case '#**=':
        result = StrictModeWrapper['#**='](thisArg, args[0], args[1]);
        break;
      case '#<<=':
        result = StrictModeWrapper['#<<='](thisArg, args[0], args[1]);
        break;
      case '#>>=':
        result = StrictModeWrapper['#>>='](thisArg, args[0], args[1]);
        break;
      case '#>>>=':
        result = StrictModeWrapper['#>>>='](thisArg, args[0], args[1]);
        break;
      case '#&=':
        result = StrictModeWrapper['#&='](thisArg, args[0], args[1]);
        break;
      case '#^=':
        result = StrictModeWrapper['#^='](thisArg, args[0], args[1]);
        break;
      case '#|=':
        result = StrictModeWrapper['#|='](thisArg, args[0], args[1]);
        break;
      // LHS property access
      case '#.=':
        result = StrictModeWrapper['#.='](thisArg, args[0]);
        break;
      // getter for super
      case 's.':
      case 's[]':
        result = args[1](args[0]);
        break;
      // super method call
      case 's()':
        result = args[2](args[0]).apply(thisArg, args[1]);
        break;
      // unary operators for super
      case 's++':
      case '++s':
      case 's--':
      case '--s':
        result = args[1].apply(thisArg, args);
        break;
      // assignment operators for super
      case 's=':
      case 's+=':
      case 's-=':
      case 's*=':
      case 's/=':
      case 's%=':
      case 's**=':
      case 's<<=':
      case 's>>=':
      case 's>>>=':
      case 's&=':
      case 's^=':
      case 's|=':
        result = args[2].apply(thisArg, args);
        break;
      // getter in 'with' statement body
      case 'w.':
      case 'w[]':
        result = args[1]();
        break;
      // function call in 'with' statement body
      case 'w()':
        result = args[2](...args[1]);
        break;
      // constructor call in 'with' statement body
      case 'wnew':
        result = args[2](...args[1]);
        break;
      // unary operators in 'with' statement body
      case 'w++':
      case '++w':
      case 'w--':
      case '--w':
        result = args[1]();
        break;
      // unary operators in 'with' statement body
      case 'wtypeof':
      case 'wdelete':
        result = args[1]();
        break;
      // LHS value in 'with' statement body (__hook__('w.=', __with__, ['p', { set ['='](v) { p = v } } ], 'context', false)['='])
      case 'w.=':
        result = args[1];
        break;
      // assignment operators in 'with' statement body
      case 'w=':
      case 'w+=':
      case 'w-=':
      case 'w*=':
      case 'w/=':
      case 'w%=':
      case 'w**=':
      case 'w<<=':
      case 'w>>=':
      case 'w>>>=':
      case 'w&=':
      case 'w^=':
      case 'w|=':
        result = args[2](args[1]);
        break;
      // default (invalid operator)
      default:
        f(); // throw TypeError: f is not a function
        result = null;
        break;
      }
    }
    return result;
  }
  // Example Hook Callback Function with Primitive Access Control
  hashContext = { 'hash': 'context', ... }; // Generated from hook.preprocess initialContext[0][1]
  trustedContext = { 'context': /trustedModules/, ... }; // Access Policies

  window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {
    console.log('hook:', context, args);
    if (!hashContext[context] ||
        !trustedContext[hashContext[context]] ||
        !(new Error('').stack.match(trustedContext[hashContext[context]]))) {
      // plus check thisArg, args, etc.
      throw new Error('Permission Denied');
    }
    return newTarget
      ? Reflect.construct(f, args)
      : thisArg
        ? f.apply(thisArg, args)
        : f(...args);
  }

Entry HTML with Service Worker

If hooking is performed run-time in Service Worker, the entry HTML page must be loaded via Service Worker so that no hook-targeted scripts are evaluated without hooking.

To achieve this, the static entry HTML has to be Encoded at build time by hook.serviceWorkerTransformers.encodeHTML(html).

Hook CLI to encode the entry HTML

  # encode src/index.html to dist/index.html
  hook --out dist/index.html src/index.html

Decoded/Original HTML (source code)

<html>
  <head>
    <script src="../thin-hook/hook.min.js?version=1&no-hook=true&hook-name=__hook__&fallback-page=index-no-sw.html&hook-property=false&service-worker-ready=true"></script>
    <!-- Hook Callback Function witout hooking properties -->
    <script no-hook>
      window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {
        ...
        return newTarget
          ? Reflect.construct(f, args)
          : thisArg
            ? f.apply(thisArg, args)
            : f(...args);
      }
    </script>
    ...
</html>

Encoded HTML (Service Worker converts it to Decoded HTML)

<html>
  <head>
    <script src="../thin-hook/hook.min.js?version=1&no-hook=true&hook-name=__hook__&fallback-page=index-no-sw.html&hook-property=false&service-worker-ready=false"></script></head></html><!--
    <C!-- Hook Callback Function without hooking properties --C>
    <script no-hook>
      window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {
        ...
        return newTarget
          ? Reflect.construct(f, args)
          : thisArg
            ? f.apply(thisArg, args)
            : f(...args);
      }
    </script>
    ...
</html>-->

Supported Syntax

  • Functions
  • Object Shorthand Methods ({ m() {} })
  • ES6 Classes (constructor, super, this, new)
  • ES6 Modules (import, export);
  • Expressions in Template Literals(`${(v => v * v)(x)}`)
  • Generator Functions (function *g() { yield X })
  • Arrow Functions (a => a, a => { return a; }, a => ({ p: a }))
  • Async Functions (async function f() {}, async method() {}, async () => {})
  • Default Parameters for Functions/Methods/Arrow Functions
  • Default Parameters with Destructuring (function f([ a = 1 ], { b = 2, x: c = 3 }) {})
  • Property Accessors (o.p, o['p'], o.p())

Install

Browsers

  bower install --save thin-hook

NodeJS

  npm install --save thin-hook

Import

Browsers

  <!-- browserified along with espree and escodegen; minified -->
  <script src="path/to/bower_components/thin-hook/hook.min.js"></script>

NodeJS

  const hook = require('thin-hook/hook.js');

API (Tentative)

  • hook(code: string, hookName: string = '__hook__', initialContext: Array = [], contextGeneratorName: string = 'method', metaHooking: boolean = true, hookProperty: boolean = true, sourceMap: object = null, asynchronous: boolean = false, compact: boolean = false, hookGlobal: boolean = true, hookPrefix: string = '_p_', initialScope: object = null)
    • code: input JavaScript as string
    • hookName: name of hook callback function
    • initialContext: typically [ ['script.js', {}] ]
    • contextGeneratorName: function property name in hook.contextGenerators
      • argument astPath = [ ['script.js', {}], ['root', rootAst], ['body', bodyAst], ..., [0, FunctionExpressionAst] ]
    • metaHooking: Enable meta hooking (run-time hooking of metaprogramming) if true
    • hookProperty: Enable hooking of object property accessors and new operators if true
    • sourceMap: Source map parameter in an object. { pathname: 'path/to/script_source.js'} Default: null
    • asynchronous: Return a Promise if true. Default: false
    • compact: Generate compact code if true. Default: false
      • Note: sourceMap is disabled when compact is true
    • hookGlobal: Hook global variable access. Must be enabled with hookProperty. Default: true
    • hookPrefix: Prefix for hook.global()._p_GlobalVariable proxy accessors. Default: _p_
      • Note: hook.global() return the global object with get/set accessors for the prefixed name
    • initialScope: Initial scope object ({ vname: true, ... }) for hooked eval scripts. Default: null
  • hook.hookHtml(html: string, hookName, url, cors, contextGenerator, contextGeneratorScripts, isDecoded, metaHooking = true, scriptOffset = 0, _hookProperty = true, asynchronous = false)
  • hook.__hook__(f: function or string, thisArg: object, args: Array, context: string, newTarget: new.target meta property)
    • minimal hook callback function with property hooking
    • f:
      • function: target function to hook
      • string: property operation to hook
        • .: get property (o.prop)
        • *: iterate over (for (p in o), for (p of o))
        • in: property existence ('p' in o)
        • (): function call (o.func())
        • =, +=, ...: assignment operation (o.prop = value)
        • p++, ++p, p--, --p: postfixed/prefixed increment/decrement operation (o.prop++)
        • delete: delete operation (delete o.prop)
        • s.: get property of super (super.prop)
        • s(): call super method (super.method())
        • s=, s+=, ...: assignment operation for super (super.prop = value)
        • s++, ++s, s--, --s: postfixed/prefixed increment/decrement operation for super (super.prop++)
        • w., w=, w(), w++, ...: operations on variables in within with statements
    • thisArg: this object for the function or the operation
    • args:
      • arguments for the function
      • [ property ] for property access operations
      • [ property, value ] for property assignment operations
      • [ property, [...args] ] for function call operations
    • context: context in the script
    • newTarget: new.target meta property for constructor calls;
      • true for new calls
      • Falsy values for non-new operations for faster detection of the operations
        • false for with statement calls
        • 0 for function calls
        • undefined for other calls
  • hook.__hook_except_properties__(f, thisArg, args, context, newTarget)
    • minimal hook callback function without property hooking
  • hook.hookCallbackCompatibilityTest(__hook__ = window[hookName], throwError = true, checkTypeError = true)
    • run-time test suite for hook callback function
    • Usage: window.__hook__ = function __hook__ (...) {}; hook.hookCallbackCompatibilityTest();
    • An error is thrown on compatibility test failure.
    • false is returned on a test failure if throwError = false
    • tests on non-callable object's function call are skipped if checkTypeError = false
  • hook.contextGenerators: object. Context Generator Functions
    • null(): context as ''
    • astPath(astPath: Array): context as 'script.js,[root]Program,body,astType,...'
    • method(astPath: Array): context as 'script.js,Class,Method' with caching, including computed method variable name
    • cachedMethod(astPath: Array): alias for method
    • cachedMethodDebug(astPath: Array): context as 'script.js,Class,Method', comparing contexts with those by "oldMethod" in console.warn() messages
    • oldMethod(astPath: Array): context as 'script.js,Class,Method' for compatibility
    • custom context generator function has to be added to this object with its unique contextGeneratorName
  • Hooked Native APIs: Automatically applied in hook() preprocessing
    • hook.global(hookCallback: function = hookName, context: string, name: string, type: string)._p_name: hooked global variable accessor when hookGlobal is true
      • type: one of 'var', 'function', 'let', 'const', 'class', 'get', 'set', 'delete', 'typeof'
    • hook.Function(hookName, initialContext: Array = [['Function', {}]], contextGeneratorName): hooked Function constructor for use in hook callback function __hook__
      • Usage: (new (hook.Function('__hook__', [['window,Function', {}]], 'method'))('return function f() {}'))()
      • Notes:
        • Avoid replacing the native API window.Function for better transparency (now commented out in the demo/hook-native-api.js)
        • NOT automatically applied in the hooking
        • Applied in the hook callback function (__hook__) instead
    • hook.FunctionArguments(hookName, initialContext: Array = [['Function', {}]], contextGeneratorName = 'method', args, isGenerator = false): generate hooked Function arguments to hand to Function constructor for use in hook callback function __hook__
      • Usage: hook.FunctionArguments('__hook__', [['window,Function', {}]], 'method', ['return function f() {}'])
      • Returns hooked args in a cloned Array
    • hook.eval(hookName, initialContext: Array = [['eval', {}]], contextGeneratorName): hooked eval function
      • Usage: hook.eval('__hook__', [['eval', {}]], 'method'))('1 + 2', (script, eval) => eval(script))
      • Note: In no-hook scripts with the hooked global eval function via hook.hook(hook.eval(...)), the evaluation is bound to the global scope unless the wrapper arrow function (script, eval) => eval(script) is defined in the local scope and specifed as the second argument of each eval() call
    • hook.setTimeout(hookName, initialContext: Array = [['setTimeout', {}]], contextGeneratorName): hooked setTimeout function
      • Note: Not automatically applied if the first argument is an (arrow) function expression
    • hook.setInterval(hookName, initialContext: Array = [['setInterval', {}]], contextGeneratorName): hooked setInterval function
      • Note: Not automatically applied if the first argument is an (arrow) function expression
    • hook.Node(hookName, initialContext: Array = [['Node', {}]], contextGeneratorName): hook textContent property
      • set textContent: hooked with context 'ClassName,set textContent'
    • hook.Element(hookName, initialContext: Array = [['Element', {}]], contextGeneratorName): hook setAttribute function
      • setAttribute('onXX', '{script in attribute}'): Script in onXX handler attribute is hooked
      • setAttribute('href', 'javascript:{script in URL}'): Script in URL "javascript:{script in URL}" is hooked
    • hook.HTMLScriptElement(hookName, initialContext: Array = [['HTMLScriptElement', {}]], contextGeneratorName): HTMLScriptElement with hooked properties
      • Note: Applied only at run time. Not applied in preprocessing. HTMLScriptElement class is the same object as the native one. hook.Node and hook.Element are called internally.
      • set textContent: Script in textContent is hooked if type is a JavaScript MIME type. Node.textContent is hooked as well.
        • Note: Scripts set by innerHTML/outerHTML/text properties are NOT executed, while text should be executed according to the standards.
      • set type: Script in this.textContent is hooked if type is a JavaScript MIME type.
      • setAttribute('type', mimeType): Script in this.textContent is hooked if mimeType is a JavaScript MIME type. Element.setAttribute is hooked as well.
    • hook.HTMLAnchorElement(hookName, initialContext: Array = [['HTMLAnchorElement', {}]]), contextGeneratorName): HTMLAnchorElement with hooked href property
      • set href: Script in URL "javascript:{script in URL}" is hooked
    • hook.HTMLAreaElement(hookName, initialContext: Array = [['HTMLAreaElement', {}]]), contextGeneratorName): HTMLAreaElement with hooked href property
      • set href: Script in URL "javascript:{script in URL}" is hooked
    • hook.Document(hookName, initialContext: Array = [['Document', {}]], contextGeneratorName): hook write function
      • write('<sc' + 'ript>{script in string}</sc' + 'ript>'): Script in HTML fragment is hooked
    • hook.with(scope: Object, ...scopes: Array of Object): Hook with statement scope object
      • with (hook.with(obj, { v1: true, v2: true, ...})) {}
    • hook.importScripts(): return hooked importScripts function for Workers, invalidating extensions other than .js and .mjs
      • Note: No arguments to pass
  • hook.hook(target: Class, ...): hook platform global object with target
    • Usage: ['Function','setTimeout','setInterval',...].forEach(name => hook.hook(hook.Function('__hook__', [[name, {}]], 'method'))
  • hook.serviceWorkerHandlers: Service Worker event handlers
    • install: 'install' event handler. Set version from the version parameter
    • activate: 'activate' event handler. Clear caches of old versions.
    • fetch: 'fetch' event handler. Cache hooked JavaScripts and HTMLs except for the main page loading hook.min.js
      • <script src="thin-hook/hook.min.js?version=1&sw-root=/&no-hook=true&hook-name=__hook__&discard-hook-errors=true&fallback-page=index-no-sw.html&hook-property=true&service-worker-ready=true"></script>: arguments from the page
        • version: default 1. Service Worker cache version. Old caches are flushed when the version is changed in the main page and reloaded. Service Worker is updated when the controlled page is detached after the reloading.
        • sw-root: optional. Set Service Worker scope
        • hook-name: default __hook__. hook callback function name
        • context-generator-name: default method. context generator callback function name
        • discard-hook-errors: true if errors in hooking are ignored and the original contents are provided. Default: true
        • fallback-page: fallback page to land if Service Worker is not available in the browser
        • no-hook-authorization: Optional. CSV of no-hook authorization tickets for no-hook scripts. Typically for ticket of no-hook authorization script itself.
          • The values are stored in hook.parameters.noHookAuthorizationPreValidated object in Service Worker
          • Add the value log-no-hook-authorization to log authorization in console
          • Note: no-hook-authorization must not exist in learning mode with hook.parameters.noHookAuthorization['*'] === true
            • Steps to update authorized no-hook scripts:
                1. Let no-hook be "learning mode" by truthy hook.parameters.noHookAuthorization['*']
                1. Remove (or temporarily rename) no-hook-authorization parameter from hook.min.js
                1. Update no-hook script(s)
                1. Clear Service Worker(s)
                1. Update version parameter for hook.min.js
                1. Check "Preserve Logs" option in debugger console
                1. Reload the page(s) with no-hook script(s)
                1. Copy and Paste values of hook.parameters.noHookAuthorizationPassed from both browser document and Service Worker to no-hook authorization script
                1. Disable "learning mode"
                1. Enable (or revive) no-hook-authorization parameter for hook.min.js with a dummy value
                1. Clear Service Worker(s)
                1. Update version parameter for hook.min.js
                1. Reload the page(s) with no-hook scripts(s)
                1. Copy and Paste the ticket for the no-hook authorization script into the no-hook-authorization parameter
                1. Update version parameter for hook.min.js
                1. Clear Service Worker(s)
                1. Reload the page(s) with no-hook script(s)
                1. Check if there are no unauthorized no-hook scripts
        • hook-property: hookProperty parameter. true if property accessors are hooked. The value affects the default value of the hookProperty parameter for hook()
        • hook-global: hookGlobal parameter. true if global variables are hooked. The value affects the default value of the hookGlobal parameter for hook()
        • hook-prefix: hookPrefix parameter. Prefix accessor names of hook.global()._p_GlobalVariableName with the value. Default: _p_
        • compact: compact parameter. Generate compact code if true. The value affects the default value of the compact parameter for hook()
        • service-worker-ready: true if the entry HTML page is decoded; false if encoded. This parameter must be at the end of the URL
      • <script src="script.js?no-hook=true"></script>: skip hooking for the source script
      • <script no-hook>...</script>: skip hooking for the embedded script
      • <script context-generator>: register a custom context generator for both Service Worker and browser document
        • <script context-generator no-hook>hook.contextGenerators.custom = function (astPath) {...}</script>: embedded script
        • <script context-generator src="custom-context-generator.js?no-hook=true"></script>: with src URL
        • Valid only in the main entry document with hook.min.js for Service Worker
        • Must be runnable in both Service Worker and browser document
        • Extensions other than context generators:
          • Set Service Worker parameters:
            • hook.parameters.cors = [ 'cors_url_1', 'cors_url_2', ... ]: specify CORS script URLs
            • hook.parameters.cors = [ (url) => url.match(/cors_url_pattern/), ... ]: specify CORS script URL detector function(s)
            • hook.parameters.opaque = [ 'opaque_url_1', 'opaque_url_2', ... ]: specify authorized opaque URLs
            • hook.parameters.opaque = [ (url) => url.match(/opaque_url_pattern/), ... ]: specify authorized opaque URL detector function(s)
          • Set no-hook Authorization Tickets:
            • hook.parameters.noHookAuthorization = { '{sha-256 hex hash for authorized no-hook script}': true, ... }: Set keys from hook.parameters.noHookAuthorizationPassed in both Document and Service Worker threads
            • hook.parameters.noHookAuthorization = { '*': true }: learning mode to detect authorization tickets
          • Specify URL patterns for no-hook scripts:
            • hook.parameters.noHook = [ 'no_hook_url_1', 'no_hook_url_2', ... ]: specify no-hook script URLs
            • hook.parameters.noHook = [ (url: URL) => !!url.href.match(/{no-hook URL pattern}/), ... ]: specify no-hook script URL detector function(s)
          • Specify URL patterns for source map target scripts:
            • hook.parameters.sourceMap = [ 'source_map_target_url_1', 'source_map_target_url_2', ... ]: specify source map target script URLs
            • hook.parameters.sourceMap = [ (url: URL) => !!url.href.match(/{source map target URL pattern}/), ... ]: specify source map target script URL detector function(s)
          • Specify URL for hook worker script:
            • hook.parameters.hookWorker = 'hook-worker.js?no-hook=true': specify hook worker script URL
          • Register Custom Event Handler:
            • if (typeof self === 'object' && self instanceof 'ServiceWorkerGlobalScope') { self.addEventListener('{event_type}', function handler(event) {...})}
          • URL for the entry page
            • hook.parameters.baseURI: Set in demo/bootstrap.js
          • Empty Document URL
            • hook.parameters.emptyDocumentUrl = new URL('./empty-document.html', baseURI);: Set in demo/bootstrap.js.
            • <iframe src="empty-document.html?url=https://host/path.html,iframe"> to specify context in iframe document
          • Bootstrap Script Tag
            • hook.parameters.bootstrap = "<script>frameElement.dispatchEvent(new Event('srcdoc-load'))</script>";: Set in demo/bootstrap.js
            • Append to the hooked srcdoc to dispatch srcdoc-load event to onload handler
          • Onload Wrapper Script
            • hook.parameters.onloadWrapper = "event.target.addEventListener('srcdoc-load', () => { $onload$ })";: Set in demo/bootstrap.js
            • Receive srcdoc-load event and trigger the original onload script
              • Note: addEventListener('load', handler) is currently called BEFORE the document from srcdoc is loaded and srcdoc-load event is fired.
          • Empty SVG to load the target URL
            • hook.parameters.emptySvg = '<?xml version="1.0"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1px" height="1px"><script>location = "$location$";</script></svg>';
          • Bootstrap Scripts for SVG
            • hook.parameters.bootstrapSvgScripts = '<script xlink:href="URL?params"></script>...'
          • Check Request callback on Fetch at Service Worker
            • hook.parameters.checkRequest = function (event, response, cache) { /* check request */ return response ; }: response - cached response if exists; See demo/disable-devtools.js
          • Root of Application Path
            • hook.parameters.appPathRoot = '/'; - The app assets are under location.origin + hook.parameters.appPathRoot
      • register as Service Worker
        • Service-Worker-Allowed HTTP response header must have an appropriate scope for the target application
      • cors=true parameter: CORS script, e.g., <script src="https://cross.origin.host/path/script.js?cors=true"></script>
  • hook.serviceWorkerTransformers:
    • encodeHtml(html: string): encode HTML for Service Worker
    • decodeHtml(html: string): decode encoded HTML for Service Worker
  • hook.hookWorkerHandler(event): onmessage handler for Hook Workers
    • Usage: onmessage = hook.hookWorkerHandler in Hook Worker script
  • hook.registerServiceWorker(fallbackUrl: string = './index-no-service-worker.html', reloadTimeout: number = 500, inactiveReloadTimeout: number = 1000):
    • Automatically called on loading hook.min.js on browsers
    • fallbackUrl: fallback URL for browsers without Service Worker
    • reloadTimeout: default: 500 (ms). Timeout to reload the page when no Service Worker is detected
    • inactiveReloadTimeout: default: 1000 (ms). Timeout to reload the page when inactive (waiting, installing) Service Worker is detected. When a state change of the Service Worker instance is detected, the page is reloaded immediately even before the timeout.
  • utils: Utilities
    • createHash: Synchronous SHA hash generator collections from sha.js
    • HTMLParser: HTML parser from htmlparser2

TODOs

  • Refine API
  • Hook Coverage
    • Hook Web Worker Scripts
    • Hook Native APIs
  • Consistent Contexts
  • Track Asynchronous Calls
  • Security Policies
    • Framework for Access Control Policies
    • Framework for Context Transition Policies
    • Modularization of Policies
  • Test Suites
  • Demo
  • Performance Optimization

License

BSD-2-Clause

0.4.0-alpha.61

2 years ago

0.4.0-alpha.60

2 years ago

0.4.0-alpha.58

2 years ago

0.4.0-alpha.59

2 years ago

0.4.0-alpha.54

2 years ago

0.4.0-alpha.55

2 years ago

0.4.0-alpha.56

2 years ago

0.4.0-alpha.57

2 years ago

0.4.0-alpha.52

2 years ago

0.4.0-alpha.53

2 years ago

0.4.0-alpha.51

3 years ago

0.4.0-alpha.50

3 years ago

0.4.0-alpha.49

5 years ago

0.4.0-alpha.48

5 years ago

0.4.0-alpha.47

5 years ago

0.4.0-alpha.46

5 years ago

0.4.0-alpha.45

5 years ago

0.4.0-alpha.44

5 years ago

0.4.0-alpha.43

5 years ago

0.4.0-alpha.42

5 years ago

0.4.0-alpha.41

5 years ago

0.4.0-alpha.40

5 years ago

0.4.0-alpha.39

5 years ago

0.4.0-alpha.38

5 years ago

0.4.0-alpha.37

5 years ago

0.4.0-alpha.36

5 years ago

0.4.0-alpha.35

5 years ago

0.4.0-alpha.34

5 years ago

0.4.0-alpha.32

5 years ago

0.4.0-alpha.33

5 years ago

0.4.0-alpha.31

5 years ago

0.4.0-alpha.30

5 years ago

0.4.0-alpha.29

5 years ago

0.4.0-alpha.28

5 years ago

0.4.0-alpha.27

5 years ago

0.4.0-alpha.26

5 years ago

0.4.0-alpha.25

5 years ago

0.4.0-alpha.24

5 years ago

0.4.0-alpha.23

5 years ago

0.4.0-alpha.21

5 years ago

0.4.0-alpha.20

5 years ago

0.4.0-alpha.19

5 years ago

0.4.0-alpha.18

5 years ago

0.4.0-alpha.17

5 years ago

0.4.0-alpha.15

5 years ago

0.4.0-alpha.16

5 years ago

0.4.0-alpha.14

5 years ago

0.4.0-alpha.13

5 years ago

0.4.0-alpha.12

5 years ago

0.4.0-alpha.11

5 years ago

0.4.0-alpha.10

5 years ago

0.4.0-alpha.9

5 years ago

0.4.0-alpha.8

5 years ago

0.4.0-alpha.7

5 years ago

0.4.0-alpha.6

5 years ago

0.4.0-alpha.5

5 years ago

0.4.0-alpha.4

5 years ago

0.4.0-alpha.3

5 years ago

0.4.0-alpha.2

6 years ago

0.4.0-alpha.1

6 years ago

0.3.7

6 years ago

0.3.6

6 years ago

0.3.5

6 years ago

0.3.4

6 years ago

0.3.3

6 years ago

0.3.2

6 years ago

0.3.1

7 years ago

0.3.0

7 years ago

0.2.6

7 years ago

0.2.5

7 years ago

0.2.4

7 years ago

0.2.3

7 years ago

0.2.2

7 years ago

0.2.1

7 years ago

0.2.0

7 years ago

0.1.13-stack.21

7 years ago

0.1.13-stack.20

7 years ago

0.1.13-stack.19

7 years ago

0.1.13-stack.18

7 years ago

0.1.13-stack.17

7 years ago

0.1.13-stack.16

7 years ago

0.1.13-stack.15

7 years ago

0.1.13-stack.14

7 years ago

0.1.13-stack.13

7 years ago

0.1.13-stack.12

7 years ago

0.1.13-stack.11

7 years ago

0.1.13-stack.10

7 years ago

0.1.13-stack.9

7 years ago

0.1.13-stack.8

7 years ago

0.1.13-stack.6

7 years ago

0.1.13-stack.5

7 years ago

0.1.13-stack.4

7 years ago

0.1.13-stack.3

7 years ago

0.1.13-stack.2

7 years ago

0.1.13-stack.1

7 years ago

0.1.13

7 years ago

0.1.12

7 years ago

0.1.11

7 years ago

0.1.10

7 years ago

0.1.9

7 years ago

0.1.8

7 years ago

0.1.7

7 years ago

0.1.6

7 years ago

0.1.5

7 years ago

0.1.4

7 years ago

0.1.3

7 years ago

0.1.2

7 years ago

0.1.1

7 years ago

0.1.0

7 years ago

0.0.257

7 years ago

0.0.256

7 years ago

0.0.255

7 years ago

0.0.254

7 years ago

0.0.253

7 years ago

0.0.252

7 years ago

0.0.251

7 years ago

0.0.250

7 years ago

0.0.249

7 years ago

0.0.248

7 years ago

0.0.247

7 years ago

0.0.246

7 years ago

0.0.245

7 years ago

0.0.244

7 years ago

0.0.243

7 years ago

0.0.242

7 years ago

0.0.241

7 years ago

0.0.240

7 years ago

0.0.239

7 years ago

0.0.238

7 years ago

0.0.237

7 years ago

0.0.236

7 years ago

0.0.235

7 years ago

0.0.234

7 years ago

0.0.233

7 years ago

0.0.232

7 years ago

0.0.231

7 years ago

0.0.230

7 years ago

0.0.229

7 years ago

0.0.228

7 years ago

0.0.227

7 years ago

0.0.226

7 years ago

0.0.225

7 years ago

0.0.224

7 years ago

0.0.223

7 years ago

0.0.222

7 years ago

0.0.221

8 years ago

0.0.220

8 years ago

0.0.219

8 years ago

0.0.218

8 years ago

0.0.217

8 years ago

0.0.216

8 years ago

0.0.215

8 years ago

0.0.214

8 years ago

0.0.213

8 years ago

0.0.212

8 years ago

0.0.211

8 years ago

0.0.210

8 years ago

0.0.209

8 years ago

0.0.208

8 years ago

0.0.207

8 years ago

0.0.206

8 years ago

0.0.205

8 years ago

0.0.204

8 years ago

0.0.203

8 years ago

0.0.202

8 years ago

0.0.201

8 years ago

0.0.200

8 years ago

0.0.199

8 years ago

0.0.198

8 years ago

0.0.197

8 years ago

0.0.196

8 years ago

0.0.195

8 years ago

0.0.194

8 years ago

0.0.193

8 years ago

0.0.192

8 years ago

0.0.191

8 years ago

0.0.190

8 years ago

0.0.189

8 years ago

0.0.188

8 years ago

0.0.187

8 years ago

0.0.186

8 years ago

0.0.185

8 years ago

0.0.184

8 years ago

0.0.183

8 years ago

0.0.182

8 years ago

0.0.181

8 years ago

0.0.180

8 years ago

0.0.179

8 years ago

0.0.178

8 years ago

0.0.177

8 years ago

0.0.176

8 years ago

0.0.175

8 years ago

0.0.174

8 years ago

0.0.173

8 years ago

0.0.172

8 years ago

0.0.171

8 years ago

0.0.170

8 years ago

0.0.169

8 years ago

0.0.168

8 years ago

0.0.167

8 years ago

0.0.166

8 years ago

0.0.165

8 years ago

0.0.164

8 years ago

0.0.163

8 years ago

0.0.162

8 years ago

0.0.161

8 years ago

0.0.160

8 years ago

0.0.159

8 years ago

0.0.158

8 years ago

0.0.157

8 years ago

0.0.156

8 years ago

0.0.155

8 years ago

0.0.154

8 years ago

0.0.153

8 years ago

0.0.152

8 years ago

0.0.151

8 years ago

0.0.150

8 years ago

0.0.149

8 years ago

0.0.148

8 years ago

0.0.147

8 years ago

0.0.146

8 years ago

0.0.145

8 years ago

0.0.144

8 years ago

0.0.143

8 years ago

0.0.142

8 years ago

0.0.141

8 years ago

0.0.140

8 years ago

0.0.139

8 years ago

0.0.138

8 years ago

0.0.137

8 years ago

0.0.136

8 years ago

0.0.135

8 years ago

0.0.134

8 years ago

0.0.133

8 years ago

0.0.132

8 years ago

0.0.131

8 years ago

0.0.130

8 years ago

0.0.129

8 years ago

0.0.128

8 years ago

0.0.127

8 years ago

0.0.126

8 years ago

0.0.125

8 years ago

0.0.124

8 years ago

0.0.123

8 years ago

0.0.122

8 years ago

0.0.121

8 years ago

0.0.120

8 years ago

0.0.119

8 years ago

0.0.118

8 years ago

0.0.117

8 years ago

0.0.116

8 years ago

0.0.115

8 years ago

0.0.114

8 years ago

0.0.113

8 years ago

0.0.112

8 years ago

0.0.111

8 years ago

0.0.110

8 years ago

0.0.109

8 years ago

0.0.108

8 years ago

0.0.107

8 years ago

0.0.106

8 years ago

0.0.105

8 years ago

0.0.104

8 years ago

0.0.103

8 years ago

0.0.102

8 years ago

0.0.101

8 years ago

0.0.100

8 years ago

0.0.99

8 years ago

0.0.98

8 years ago

0.0.97

8 years ago

0.0.96

8 years ago

0.0.95

8 years ago

0.0.94

8 years ago

0.0.93

8 years ago

0.0.92

8 years ago

0.0.91

8 years ago

0.0.90

8 years ago

0.0.89

8 years ago

0.0.88

8 years ago

0.0.87

8 years ago

0.0.86

8 years ago

0.0.85

8 years ago

0.0.84

8 years ago

0.0.83

8 years ago

0.0.82

8 years ago

0.0.81

8 years ago

0.0.80

8 years ago

0.0.79

8 years ago

0.0.78

8 years ago

0.0.77

8 years ago

0.0.76

8 years ago

0.0.75

8 years ago

0.0.74

8 years ago

0.0.73

8 years ago

0.0.72

8 years ago

0.0.71

8 years ago

0.0.70

8 years ago

0.0.69

8 years ago

0.0.68

8 years ago

0.0.67

8 years ago

0.0.66

8 years ago

0.0.65

8 years ago

0.0.64

8 years ago

0.0.63

8 years ago

0.0.62

8 years ago

0.0.61

8 years ago

0.0.60

8 years ago

0.0.59

8 years ago

0.0.58

8 years ago

0.0.57

8 years ago

0.0.56

8 years ago

0.0.55

8 years ago

0.0.54

8 years ago

0.0.53

8 years ago

0.0.52

8 years ago

0.0.51

8 years ago

0.0.50

8 years ago

0.0.49

8 years ago

0.0.48

8 years ago

0.0.47

8 years ago

0.0.46

8 years ago

0.0.45

8 years ago

0.0.44

8 years ago

0.0.43

8 years ago

0.0.42

8 years ago

0.0.41

8 years ago

0.0.40

8 years ago

0.0.39

8 years ago

0.0.38

8 years ago

0.0.37

8 years ago

0.0.36

8 years ago

0.0.35

8 years ago

0.0.34

8 years ago

0.0.33

8 years ago

0.0.32

8 years ago

0.0.31

8 years ago

0.0.30

8 years ago

0.0.29

8 years ago

0.0.28

8 years ago

0.0.27

8 years ago

0.0.26

8 years ago

0.0.25

8 years ago

0.0.24

8 years ago

0.0.23

8 years ago

0.0.22

8 years ago

0.0.21

8 years ago

0.0.20

8 years ago

0.0.19

8 years ago

0.0.18

8 years ago

0.0.17

8 years ago

0.0.16

8 years ago

0.0.15

8 years ago

0.0.14

8 years ago

0.0.13

8 years ago

0.0.12

8 years ago

0.0.11

9 years ago

0.0.10

9 years ago

0.0.9

9 years ago

0.0.8

9 years ago

0.0.7

9 years ago

0.0.5

9 years ago

0.0.4

9 years ago

0.0.3

9 years ago

0.0.2

9 years ago

0.0.1

9 years ago