{"id":17965357,"url":"https://github.com/t2ym/thin-hook","last_synced_at":"2025-04-03T20:19:00.179Z","repository":{"id":16417369,"uuid":"79903990","full_name":"t2ym/thin-hook","owner":"t2ym","description":"Thin Hook Preprocessor","archived":false,"fork":false,"pushed_at":"2025-04-01T09:17:10.000Z","size":171700,"stargazers_count":4,"open_issues_count":120,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-01T09:21:12.727Z","etag":null,"topics":["access-control-list","es6","hooks","mandatory-access-control","service-worker"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/t2ym.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-24T11:06:16.000Z","updated_at":"2025-04-01T09:17:19.000Z","dependencies_parsed_at":"2024-10-29T13:04:01.452Z","dependency_job_id":null,"html_url":"https://github.com/t2ym/thin-hook","commit_stats":{"total_commits":1657,"total_committers":1,"mean_commits":1657.0,"dds":0.0,"last_synced_commit":"c801304c4d2e5c5c384d2cc40368182f2fc9fe46"},"previous_names":[],"tags_count":367,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Fthin-hook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Fthin-hook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Fthin-hook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t2ym%2Fthin-hook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/t2ym","download_url":"https://codeload.github.com/t2ym/thin-hook/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247070926,"owners_count":20878586,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["access-control-list","es6","hooks","mandatory-access-control","service-worker"],"created_at":"2024-10-29T12:41:53.963Z","updated_at":"2025-04-03T20:19:00.152Z","avatar_url":"https://github.com/t2ym.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm version](https://badge.fury.io/js/thin-hook.svg)](https://badge.fury.io/js/thin-hook)\n[![Bower version](https://badge.fury.io/bo/thin-hook.svg)](https://badge.fury.io/bo/thin-hook)\n\n\u003cimg src=\"https://raw.githubusercontent.com/wiki/t2ym/thin-hook/thin-hook-logo.svg?sanitize=true\" width=\"768px\"\u003e\n\n# thin-hook\n\nThin Hook Preprocessor (experimental)\n\n## Notes\n\n- **[Vulnerability Fix]** Since [0.4.0-alpha.45](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.45) with [Fix #398 Unchain policy objects to `Object.prototype`](https://github.com/t2ym/thin-hook/issues/398), access policy objects are immune to contaminated `Object.prototype` properties when `targetConfig.policy.unchainAcl = true` is configured (which is disabled by default for compatibility). Prior to this version, writable `Object.prototype` properties can contaminate access policy objects.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.45](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.45) with [Fix #399 Recognize acl for `Object.prototype` object properly](https://github.com/t2ym/thin-hook/issues/399), `acl.Object[S_PROTOTYPE]` policies for `Object.prototype` properties are properly applied when `targetConfig.policy.unchainAcl = true` is configured (which is disabled by default for compatibility). Prior to this version, `acl.Object` policies are incorrectly applied for `Object.prototype` properties in some cases.\n- **[Enhancement]** Since [0.4.0-alpha.28](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.28) with [Issue #376 Support ES modules](https://github.com/t2ym/thin-hook/issues/376), ACL policies are applied to ES modules by hooking ES module objects. This feature is optional and can be disabled by `hook.parameters.importMapper = null` in `demo/bootstrap.js`\n- **[Vulnerability Fix]** Since [0.4.0-alpha.25](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.25) with [Fix #369 Block DOM intrusion by Browser Extensions](https://github.com/t2ym/thin-hook/issues/369), the application hangs up with an alert message on DOM intrusion by Browser Extensions. Prior to this version, Browser Extensions can intrude into DOM and manipulate contents.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.24](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.24) with [Fix #368 Check Service Worker cache integrity](https://github.com/t2ym/thin-hook/issues/368), integrity of Service Worker cache contents is verified with HMAC keys. Prior to this version, corrupted Service Worker cache contents can intrude into the application.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.22](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.22) with [Fix #363 Block blob URLs](https://github.com/t2ym/thin-hook/issues/363), blob URLs are blocked except for `\u003ca download=\"filename\" href=\"blob:...\"\u003eDownload Link\u003c/a\u003e`. Prior to this version, documents with blob URLs bypass Service Worker.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.22](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.22) with [Fix #362 Option to block `\u003cembed\u003e` and `\u003cobject\u003e` elements](https://github.com/t2ym/thin-hook/issues/362), the application hangs up on `\u003cembed\u003e` and `\u003cobject\u003e` activities with `hook.parameters.hangUpOnEmbedAndObjectElement = true`. Prior to this version, `\u003cembed\u003e` and `\u003cobject\u003e` documents can bypass Service Worker with Chrome Canary 86.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.21](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.21) with [Fix #355 Treat proxy objects as alias objects in ACL](https://github.com/t2ym/thin-hook/issues/355), ACL is properly applied for proxy objects created via `new Proxy(target, handler)` and `Proxy.revocable(target, handler)` as with their original `target` objects. Prior to this version, ACL for the `target` objects are not applied to proxy objects.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.20](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.20) with [Fix #350 Append the target value to hooked arguments and pick it up in `Policy.defaultAcl()`](https://github.com/t2ym/thin-hook/issues/350), ACL is properly applied for `with`-scoped values in function calls and constructor calls. Prior to this version, calls to `with`-scoped functions can skip ACLs for their reference values.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.20](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.20) with [Fix #349 Hook local function calls in `with` clause](https://github.com/t2ym/thin-hook/issues/349), local function calls in `with` clause are property hooked. Prior to this version, local function calls in `with` clause are not hooked. This is a regression issue from the fix for [Fix #339 Local variables in a with block are mistreated as global variables in ACL #339](https://github.com/t2ym/thin-hook/issues/339).\n- **[Vulnerability Fix]** Since [0.4.0-alpha.20](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.20) with [Fix #348 Hook tagged template literals as function calls](https://github.com/t2ym/thin-hook/issues/348), tag functions of tagged template literals are hooked as function calls. Prior to this version, calls to tag functions of tagged template literals are not hooked.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.19](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.19) with [Fix #347 Apply ACL for properties of primitive values](https://github.com/t2ym/thin-hook/issues/347), `acl.type[S_PROTOTYPE][S_INSTANCE]` ACLs for primitive type classes `String`, `Number`, `Boolean`, `Symbol`, and `BigInt` are applied to properties of primitive values. Prior to this version, ACL is not applied to properties of primitive values.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.17](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.17) with [Fix #344 Normalize `with` namespace objects before bound function detection](https://github.com/t2ym/thin-hook/issues/344), bound function calls in a `with` clause is properly detected. Prior to this version, ACL for unbound original function is not applied for bound function calls in a `with` clause.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.16](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.16) with [Fix #342 Chain `acl[S_DEFAULT][S_PROTOTYPE][S_INSTANCE]` to `acl.Object[S_PROTOTYPE][S_INSTANCE]`](https://github.com/t2ym/thin-hook/issues/342), `acl.Object[S_PROTOTYPE][S_INSTANCE]` is applied for anonymous object properties. Prior to this version, `acl.Object[S_PROTOTYPE][S_INSTANCE]` is not applied anonymous object properties.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.15](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.15) with [Fix #341 Apply ACL to all sources of Object.assign()](https://github.com/t2ym/thin-hook/issues/341), ACL is applied for all sources of `Object.assign()` even they contain falsy values. Prior to this version, ACL is not applied for sources of `Object.assign()` if the first source is not an object like `undefined`.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.13](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.13) with [Fix #336 Apply ACL for super classes/objects of global classes/objects with no dedicated ACLs](https://github.com/t2ym/thin-hook/issues/336), ACL is applied for super classes/objects of global classes/objects and instances of global classes even without dedicated ACLs. Prior to this version, ACL is not applied for super classes/objects of global classes/objects and instances of global classes without dedicated ACLs.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.9](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.9) with [Fix #336 Apply ACL for super classes/objects](https://github.com/t2ym/thin-hook/issues/336), ACL is applied for super classes/objects of non-global classes/objects and instances of non-global classes. Prior to this version, ACL is not applied for super classes/objects of non-global classes/objects and instances of non-global classes.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.8](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.8) with [Fix #334 Apply ACL for reading the target property of `receiver` in `Reflect.get()`](https://github.com/t2ym/thin-hook/issues/334), ACL is applied for `receiver` in `Reflect.get()` to read the target property. Prior to this version, ACL is not applied for `receiver` in `Reflect.get()`.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.7](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.7) with [Fix #333 Check constructors of Object.assign() source objects](https://github.com/t2ym/thin-hook/issues/333), ACL is applied for class instances as source objects in `Object.assign()` by checking their constructors. Prior to this version, ACL  for class instances as source objects in `Object.assign()` is not applied while ACL for global objects is applied properly. This fix is to supplement the fix for [Fix #324 Apply ACL for S_TARGETED normalized properties with S_ALL normalized property](https://github.com/t2ym/thin-hook/issues/324).\n- **[Vulnerability Fix]** Since [0.4.0-alpha.6](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.6) with [Fix #331 Check for all property access of destructured argument objects](https://github.com/t2ym/thin-hook/issues/331), destructured argument objects of functions are checked against all property access when called. Prior to this version, all properties of destructured argument objects of functions can be read without S_ALL access.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.5](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.5) with [Fix #327 Hook RHS values of ObjectPattern and ArrayPattern for checking all property access](https://github.com/t2ym/thin-hook/issues/327), RHS values of ObjectPattern and ArrayPattern are hooked for checking all property access of the target values. Prior to this version, all properties of RHS values in ObjectPattern and ArrayPattern can be iterated without S_ALL access.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.4](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.4) with [Fix #325 Track global objects set via defineProperty(), etc.](https://github.com/t2ym/thin-hook/issues/325), global objects set via `Object.defineProperty()`, etc. are properly tracked for ACL. Prior to this version, ACL is skipped for global objects set via `Object.defineProperty()`, etc.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.3](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.3) with [Fix #324 Apply ACL for S_TARGETED normalized properties with S_ALL normalized property](https://github.com/t2ym/thin-hook/issues/324), source objects in `Object.assign()` are checked against ACL for reading all properties. Prior to this version, ACL is skipped for source objects in `Object.assign()` and any enumerable properties in the source objects can be assigned to the target object.\n- **[Vulnerability Fix]** Since [0.4.0-alpha.2](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.2) with [Fix #310 Hang up with an infinite loop if a hacked Service Worker tries to replace the entry page](https://github.com/t2ym/thin-hook/issues/310), the entry page hangs up if a hacked Service Worker tries to navigate the entry page. Prior to this version, a hacked Service Worker from an MITM attacker can replace the entry page with an arbitrary URL.\n- **[Vulnerability Fix]** Since [0.3.7](https://github.com/t2ym/thin-hook/releases/tag/0.3.7) with [Fix #319 Check HTML/SVG extensions and content-type case-insensitively](https://github.com/t2ym/thin-hook/issues/319), extensions and content-type for HTML and SVG are checked case-insensitively. Prior to this version, extensions and content-type for HTML and SVG are check case-sensitively and some contents with capital letters such as *.HTML with content-type TEXT/HTML are not detected as HTML and can bypass hooking.\n- **[Vulnerability Fix]** Since [0.3.6](https://github.com/t2ym/thin-hook/releases/tag/0.3.6) with [Fix #318 Check Worker extensions](https://github.com/t2ym/thin-hook/issues/318), extensions for Worker URLs are checked against .m?js. Prior to this version, extensions for Worker URLs are not checked and workers with irregular extensions can bypass hooking.\n- **[Vulnerability Fix]** Since [0.3.5](https://github.com/t2ym/thin-hook/releases/tag/0.3.5) with [Fix #316 Redirect top SVG to about:blank](https://github.com/t2ym/thin-hook/issues/316), top SVG document is redirected to about:blank. Prior to this version, top SVG document can invalidate disable-devtools.js and DevTools is unexpectedly enabled.\n- **[Vulnerability Fix]** Since [0.3.4](https://github.com/t2ym/thin-hook/releases/tag/0.3.4) with [Fix #314 Check content-type for HTML and SVG as well as extensions](https://github.com/t2ym/thin-hook/issues/314), content-type is checked for HTML/SVG detection as well as extensions. Prior to this version, HTML/SVG responses with irregular extensions are not detected as HTML/SVG and thus not hooked.\n- **[Vulnerability Fix]** Since [0.3.3](https://github.com/t2ym/thin-hook/releases/tag/0.3.3) with [Fix #313 GET errorReport.json with 307 about:blank response](https://github.com/t2ym/thin-hook/issues/313), 307 redirect to `about:blank` is responded for GET errorReport.json request. Prior to this version, 404 Not Found is responded for GET errorReport.json, whose HTML contents in iframe can be accessed bypassing access policies.\n- **[Feature Enhancements]** Since [0.3.0](https://github.com/t2ym/thin-hook/releases/tag/0.3.0) with [Fix #284 Additional Content-Types in cache-bundle.json](https://github.com/t2ym/thin-hook/issues/284), extended metadata are supported in `cache-bundle.json` to add additional cacheable content types. This README document is updated to describe the new features and their configurations.\n- **[Feature Enhancements]** Since [0.2.0](https://github.com/t2ym/thin-hook/releases/tag/0.2.0) with [Fix #266 Block access via automation like puppeteer](https://github.com/t2ym/thin-hook/issues/266), there are many significant changes on global object access and hooking. ACL is basically compatible with prior versions but extra configurations for new features are required. This README document is updated to describe the new features and their configurations.\n- **[Vulnerability Fix]** Since [0.1.11](https://github.com/t2ym/thin-hook/releases/tag/0.1.11) with [Fix #265 Attach context to wrapper property name for global object access](https://github.com/t2ym/thin-hook/issues/265), the correct contexts are used for global object access in self-assignment. Prior to this version, the context for the RHS value in self-assignment is incorrectly used for the access to the object.\n- **[Vulnerability Fix]** Since [0.1.9](https://github.com/t2ym/thin-hook/releases/tag/0.1.9) with [Fix #263 Use the current context for global object access](https://github.com/t2ym/thin-hook/issues/263), the correct contexts are used for global object read/write/call access. Prior to this version, the context for the first access to the target global object is incorrectly used for the following access to the object.\n- **[Configuration]** Since [0.0.250](https://github.com/t2ym/thin-hook/releases/tag/0.0.250) with [Fix #252 Block direct access to source codes](https://github.com/t2ym/thin-hook/issues/252) and [Fix #254 Block direct access to source codes even after the app shutdown](https://github.com/t2ym/thin-hook/issues/254), direct access to source codes are blocked. `hook.parameters.appPathRoot = '/';` in `demo/disable-devtools.js` can be configured to set the root of the application assets.  Prior to this version, direct access to source codes are allowed.\n- **[Vulnerability Fix]** Since [0.0.243](https://github.com/t2ym/thin-hook/releases/tag/0.0.243) with [Fix #250 Hook scripts in SVG and block data:/blob: URLs for SVG](https://github.com/t2ym/thin-hook/issues/250), scripts in SVG are hooked and `blob:` and `data:` URLs are blocked for SVG. Prior to this version, scripts in SVG are not hoooked and `blob:` and `data:` URLs are allowed for SVG. `\u003cobject data=\"inline-script.svg\"\u003e\u003c/object\u003e`, `\u003cembed src=\"inline-script.svg\"\u003e`, `\u003ciframe src=\"inline-script.svg\"\u003e\u003c/iframe\u003e`\n- **[Vulnerability Fix]** Since [0.0.239](https://github.com/t2ym/thin-hook/releases/tag/0.0.239) with [Fix #249 Block blob: URLs for Worker](https://github.com/t2ym/thin-hook/issues/249), `blob:` and `data:` URLs are blocked for `Worker` and `SharedWorker`. Prior to this version, `blob:` and `data:` URLs are allowed for `Worker` and `SharedWorker`.\n- **[Vulnerability Fix]** Since [0.0.236](https://github.com/t2ym/thin-hook/releases/tag/0.0.236) with [Fix #247 Hook script.text property](https://github.com/t2ym/thin-hook/issues/247), script.text property is properly hooked. Prior to this version, script.text property is not hooked.\n- **[Vulnerability Fix]** Since [0.0.236](https://github.com/t2ym/thin-hook/releases/tag/0.0.236) with [Fix #246 Handle non-http protocols in iframe.src, script.src properly](https://github.com/t2ym/thin-hook/issues/246), non-http protocols in iframe src and script src are handled properly. Prior to this version, non-http protocols in iframe src and script src are not handled properly.\n- **[Vulnerability Fix]** Since [0.0.235](https://github.com/t2ym/thin-hook/releases/tag/0.0.235) with [Fix #245 no-hook-authorization parameter is missing in sub documents](https://github.com/t2ym/thin-hook/issues/245), unauthorized no hook scripts are blocked in sub documents. Prior to this version, unauthorized no hook scripts in sub documents are not blocked.\n- **[Vulnerability Fix]** Since [0.0.233](https://github.com/t2ym/thin-hook/releases/tag/0.0.233) with [Fix #242 Hook iframe.srcdoc](https://github.com/t2ym/thin-hook/issues/242), `iframe.srcdoc` is hooked as `onload` attribute. Prior to this version, `iframe.srcdoc` is not hooked.\n- **[Vulnerability Fix]** Since [0.0.232](https://github.com/t2ym/thin-hook/releases/tag/0.0.232) with [Fix #241 AsyncFunction() is not hooked](https://github.com/t2ym/thin-hook/issues/241), `AsyncFunction('script')` is properly hooked. Prior to this version, `AsyncFunction('script')` is not hooked. `AsyncFunction = (async function() {}).constructor`\n- **[Vulnerability Fix]** Since [0.0.231](https://github.com/t2ym/thin-hook/releases/tag/0.0.231) with [Fix #240 object.Function() is not hooked](https://github.com/t2ym/thin-hook/issues/240), `object.Function('script')` is properly hooked. Prior to this version, `object.Function('script')` is not hooked.\n- **[Vulnerability Fix]** Since [0.0.230](https://github.com/t2ym/thin-hook/releases/tag/0.0.230) with [Fix #239 Full ACLs for iframe.contentWindow](https://github.com/t2ym/thin-hook/issues/239), full ACLs for iframe.contentWindow are properly applied. Prior to this version, only partial ACLs for iframe.contentWindow are applied.\n- **[Vulnerability Fix]** Since [0.0.229](https://github.com/t2ym/thin-hook/releases/tag/0.0.229) with [Fix #238 No ACLs for iframe.contentWindow](https://github.com/t2ym/thin-hook/issues/238), global object ACLs for iframe.contentWindow are properly applied. Prior to this version, global object ACLs for iframe.contentWindow are not applied.\n- **[Vulnerability Fix]** Since [0.0.228](https://github.com/t2ym/thin-hook/releases/tag/0.0.228) with [Fix #234 Global ACLs are not applied in web workers](https://github.com/t2ym/thin-hook/issues/234), ACLs for global objects in web workers are properly applied. Prior to this version, ACLs for global objects in web workers are not applied.\n- **[Performance Optimization]** `__hook__acl` in `demo/hook-callback.js` should be used as it is much faster than `__hook__` as described in [Fix #230](https://github.com/t2ym/thin-hook/issues/230). Modification: `Object.defineProperty(_global, '__hook__', { configurable: false, enumerable: false, writable: false, value: hookCallbacks.__hook__acl });`\n- **[ACL Compatibility]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.225) with [Fix #229 Exclude Multiple ACLs for global object properties](https://github.com/t2ym/thin-hook/issues/229), ACLs for the global object properties (`top`, `parent`, `frames`, `global`, `_global`, etc.) other than the main global object property (`window` in the main document, `self` in workers) are applied only for access like `window.top`. In 0.0.224, all the ACLs for the global object properties are applied for every global object access, which is redundant.\n- **[Vulnerability Fix]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.224) with [Fix #227 Private API registered in strict mode](https://github.com/t2ym/thin-hook/issues/227), ACLs for private APIs registered to the global object in strict mode are properly applied. Prior to this version, ACLs for private APIs registered to the global object in strict mode are not applied.\n- **[ACL Compatibility]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.225) with [Fix #226 Multiple ACLs](https://github.com/t2ym/thin-hook/issues/226), `_globalObjects` is a `SetMap` object defined in `hook-callback.js` and `_globalObjects.get(obj)` return a `Set` object containing `string`s. All the ACLs for the set of `string`s are applied for the object. Prior to this version, `_globalObjects` is a `Map` object and `_globalObjects.get(obj)` returns a `string`.\n- **[ACL Compatibility]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.225) with [Fix #226 Multiple ACLs](https://github.com/t2ym/thin-hook/issues/226), `_blacklistObjects` is deprecated.\n- **[ACL Compatibility]** Since [0.0.216](https://github.com/t2ym/thin-hook/releases/tag/0.0.216) with [Fix #217](https://github.com/t2ym/thin-hook/issues/217), `delete` operations require `'W'` permission as they can delete properties with customized descriptors. Prior to this version, `delete` operations require `'w'` permission.\n- **[ACL Compatibility]** Since [0.0.214](https://github.com/t2ym/thin-hook/releases/tag/0.0.214) with [Fix #215](https://github.com/t2ym/thin-hook/issues/215), `'R'` and `'W'` opTypes are introduced for getting/setting property descriptors, i.e., contexts to access descriptors must have explicit `'R'` and/or `'W'` permissions for the target properties.  Prior to [0.0.213](https://github.com/t2ym/thin-hook/releases/tag/0.0.213), property descriptors can be accessed by mere `'r'` and/or `'w'` permissions.\n- **[Vulnerability Fix]** Since [0.0.211](https://github.com/t2ym/thin-hook/releases/tag/0.0.211) with [Fix #211](https://github.com/t2ym/thin-hook/issues/211), bypassing of ACL for global objects by dummy custom element definition is avoided. Prior to this version, ACL can be skipped by defining dummy custom elements by standard elements as constructor classes.\n- **[Vulnerability Fix]** Since [0.0.209](https://github.com/t2ym/thin-hook/releases/tag/0.0.209) with [Fix #210](https://github.com/t2ym/thin-hook/issues/210), bypassing of ACL for global objects by cloing them to other global objects is avoided. Prior to this version, ACL can be skipped by cloing global objects.\n- **[Vulnerability Fix]** Since [0.0.205](https://github.com/t2ym/thin-hook/releases/tag/0.0.205) with [Fix #208](https://github.com/t2ym/thin-hook/issues/208), scripts via `document.writeln()` are hooked as in `document.write()`. Prior to this version, scripts via `document.writeln()` are not hooked.\n- **[Vulnerability Fix]** Since [0.0.203](https://github.com/t2ym/thin-hook/releases/tag/0.0.203) with [Fix #207](https://github.com/t2ym/thin-hook/issues/207), `textContent` of `script` elements are always treated as JavaScript scripts regardless of their configured MIME types (`type` property/attribute).  Prior to this version, `textContent` of `script` elements containing `__hook__` as strings can be mistaken as **HOOKED** scripts and run without hooking.\n- **[Context Generator Compatibility]** Since [0.0.148](https://github.com/t2ym/thin-hook/releases/tag/0.0.148) with [#144](https://github.com/t2ym/thin-hook/issues/144), the old context generator `\"method\"` is renamed as `\"oldMethod\"` and the `\"cachedMethod\"` is renamed as `\"method\"` and become the new default context generator. The `\"cachedMethod\"` remains as an alias for the new `\"method\"` context generator. There are slight changes in the new `\"method\"` context generator. A warning message is shown on the debug console to notify the change.\n\n| old name | new name | feature |\n|:-----:|:-----:|:-----|\n| `method` | `oldMethod` | `script.js,Class,method` |\n| `cachedMethod` | `method` | `script.js,Class,method` including computed property names |\n- **[Hook Callback Compatibility]** Since [0.0.149](https://github.com/t2ym/thin-hook/releases/tag/0.0.149) with [#123](https://github.com/t2ym/thin-hook/issues/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. \n- **[Opaque URL Authorization]** Since [0.0.178](https://github.com/t2ym/thin-hook/releases/tag/0.0.178) with [#178](https://github.com/t2ym/thin-hook/issues/178), all opaque content URLs must be authorized via `hook.parameters.opaque = [ 'opaque_url', ..., (url) =\u003e url.match(/opaque_url_pattern/), ... ]` configuration.\n\n### Native API Access Graph generated via hook callback function (view2 of thin-hook/demo/)\n\n\u003cimg src=\"https://raw.githubusercontent.com/wiki/t2ym/thin-hook/native_api_access_graph.png\" width=\"768px\"\u003e\n\n[Demo](https://t2ym.github.io/thin-hook/components/thin-hook/demo/index.html) on GitHub Pages\n\n### Input\n```javascript\n  class C {\n    add(a = 1, b = 2) {\n      let plus = (x, y) =\u003e x + y;\n      return plus(a, b);\n    }\n  } \n```\n\n### Hooked Output\n```javascript\n  const __context_mapper__ = $hook$.$(__hook__, [\n    'examples/example2.js,C',\n    '_p_C;examples/example2.js,C',\n    'examples/example2.js,C,add',\n    'examples/example2.js,C,add,plus'\n  ]);\n  $hook$.global(__hook__, __context_mapper__[0], 'C', 'class')[__context_mapper__[1]] = class C {\n    add(a, b) {\n      return __hook__((a = 1, b = 2) =\u003e {\n        let plus = (...args) =\u003e __hook__((x, y) =\u003e x + y, null, args, __context_mapper__[3]);\n        return __hook__(plus, null, [\n          a,\n          b\n        ], __context_mapper__[2], 0);\n      }, null, arguments, __context_mapper__[2]);\n    }\n  };\n```\n\n### Preprocess\n\n```javascript\n  const hook = require('thin-hook/hook.js');\n  let code = fs.readFileSync('src/target.js', 'UTF-8');\n  let initialContext = [['src/target.js', {}]];\n  let gen = hook(code, '__hook__', initialContext, 'hash');\n  fs.writeFileSync('hooked/target.js', gen);\n  fs.writeFileSync('hooked/target.js.contexts.json', JSON.stringify(contexts, null, 2));\n```\n\n### Context Generator Function (customizable)\n\n```javascript\n  // Built-in Context Generator Function\n  hook.contextGenerators.method = function generateMethodContext(astPath) {\n    return astPath.map(([ path, node ], index) =\u003e node \u0026\u0026 node.type\n      ? (node.id \u0026\u0026 node.id.name ? node.id.name : (node.key \u0026\u0026 node.key.name\n        ? (node.kind === 'get' || node.kind === 'set' ? node.kind + ' ' : node.static ? 'static ' : '') + node.key.name : ''))\n      : index === 0 ? path : '').filter(p =\u003e p).join(',');\n  }\n```\n\n```javascript\n  // Example Custom Context Generator Function with Hashing\n  const hashSalt = '__hash_salt__';\n  let contexts = {};\n\n  hook.contextGenerators.hash = function generateHashContext(astPath) {\n    const hash = hook.utils.createHash('sha256');\n    let hashedInitialContext = astPath[0][0];\n    astPath[0][0] = contexts[hashedInitialContext] || astPath[0][0];\n    let methodContext = hook.contextGenerators.method(astPath);\n    astPath[0][0] = hashedInitialContext;\n    hash.update(hashSalt + methodContext);\n    let hashContext = hash.digest('hex');\n    contexts[hashContext] = methodContext;\n    return hashContext;\n  }\n```\n\n```javascript\n{\n  // Authorization Tickets for no-hook scripts\n  // Ticket for this script itself is specified in URL of script tag as\n  // hook.min.js?no-hook-authorization={ticket}\n  // Note: no-hook-authorization must not exist in learning mode\n  let noHookAuthorization = {\n    // '*' is for learning mode to detect authorization tickets in \n    //   hook.parameters.noHookAuthorizationPassed,\n    //   hook.parameters.noHookAuthorizationFailed\n    // JSONs are output to console in the learning mode\n    //'*': true,\n    \"35ae97a3305b863af7eb0ac75c8679233a2a7550e4c3046507fc9ea182c03615\": true,\n    \"16afd3d5aa90cbd026eabcc4f09b1e4207a7042bc1e9be3b36d94415513683ed\": true,\n    \"ae11a06c0ddec9f5b75de82a40745d6d1f92aea1459e8680171c405a5497d1c8\": true,\n    \"5b7ebf7b0b2977d44f47ffa4b19907abbc443feb31c343a6cbbbb033c8deb01a\": true,\n    \"c714633723320be54f106de0c50933c0aeda8ac3fba7c41c97a815ed0e71594c\": true,\n    \"2f43d927664bdfcbcb2cc4e3743652c7eb070057efe7eaf43910426c6eae7e45\": true,\n    \"b397e7c81cca74075d2934070cbbe58f345d3c00ff0bc04dc30b5c67715a572f\": true,\n    \"02c107ea633ed697acc12e1b3de1bcf2f0ef7cafe4f048e29553c224656ecd7a\": true,\n    \"aebb23ce36eb6f7d597d37727b4e6ee5a57aafc564af2d65309a9597bfd86625\": true\n  };\n  let hidden;\n  const passcode = 'XX02c107ea633ed697acc12e1b3de1bcf2f0ef7cafe4f048e29553c224656ecd7a';\n  if (typeof self === 'object' \u0026\u0026 self.constructor.name === 'ServiceWorkerGlobalScope') {\n    // Service Worker\n    let reconfigure = false;\n    if (hook.parameters.noHookAuthorization) {\n      if (Object.getOwnPropertyDescriptor(hook.parameters, 'noHookAuthorization').configurable) {\n        reconfigure = true;\n      }\n    }\n    else {\n      reconfigure = true;\n    }\n    if (reconfigure) {\n      Object.defineProperty(hook.parameters, 'noHookAuthorization', {\n        configurable: false,\n        enumerable: true,\n        get() {\n          return hidden;\n        },\n        set(value) {\n          if (value \u0026\u0026 value.passcode === passcode) {\n            delete value.passcode;\n            Object.freeze(value);\n            hidden = value;\n          }\n        }\n      });\n    }\n    noHookAuthorization.passcode = passcode;\n    hook.parameters.noHookAuthorization = noHookAuthorization;\n  }\n  else {\n    // Browser Document\n    Object.defineProperty(hook.parameters, 'noHookAuthorization', {\n      configurable: false,\n      enumerable: true,\n      writable: false,\n      value: Object.freeze(noHookAuthorization)\n    });\n  }\n  if (!noHookAuthorization['*']) {\n    Object.seal(hook.parameters.noHookAuthorizationPassed);\n  }\n}\n{\n  // source map target filters\n  hook.parameters.sourceMap = [\n    url =\u003e location.origin === url.origin \u0026\u0026 url.pathname.match(/^\\/components\\/thin-hook\\/demo\\//)\n  ];\n  // hook worker script URL\n  hook.parameters.hookWorker = `hook-worker.js?no-hook=true`;\n}\n```\n\n```javascript\n// Hook worker script (demo/hook-worker.js)\n//\n// Configuration:\n//   hook.parameters.hookWorker = `hook-worker.js?no-hook=true`;\nimportScripts('../hook.min.js?no-hook=true', 'context-generator.js?no-hook=true', 'bootstrap.js?no-hook=true');\nonmessage = hook.hookWorkerHandler;\n```\n\n```html\n  \u003c!-- Example Custom Context Generator for Service Worker and Browser Document --\u003e\n  \u003cscript src=\"bower_components/thin-hook/hook.min.js?version=1\u0026no-hook=true\u0026hook-name=__hook__\u0026context-generator-name=method2\u0026fallback-page=index-fb.html\u0026service-worker-ready=true\"\u003e\u003c/script\u003e\n  \u003cscript context-generator src=\"custom-context-generator.js?no-hook=true\"\u003e\u003c/script\u003e\n  \u003cscript context-generator no-hook\u003e\n  {\n    hook.contextGenerators.method2 = function generateMethodContext2(astPath) {\n      return astPath.map(([ path, node ], index) =\u003e node \u0026\u0026 node.type\n        ? (node.id \u0026\u0026 node.id.name ? node.id.name : (node.key \u0026\u0026 node.key.name\n          ? (node.kind === 'get' || node.kind === 'set' ? node.kind + ' ' : node.static ? 'static ' : '') + node.key.name : ''))\n        : index === 0 ? path : '').filter(p =\u003e p).join(',') +\n          (astPath[astPath.length - 1][1].range ? ':' + astPath[astPath.length - 1][1].range[0] + '-' + astPath[astPath.length - 1][1].range[1] : '');\n    }\n    Object.freeze(hook.contextGenerators);\n    // CORS script list\n    hook.parameters.cors = [\n      'https://raw.githubusercontent.com/t2ym/thin-hook/master/examples/example1.js',\n      (url) =\u003e { let _url = new URL(url); return _url.hostname !== location.hostname \u0026\u0026 ['www.gstatic.com'].indexOf(_url.hostname) \u003c 0; }\n    ];\n    // Authorized opaque URL list\n    hook.parameters.opaque = [\n      'https://www.gstatic.com/charts/loader.js',\n      (url) =\u003e {\n        let _url = new URL(url);\n        return _url.hostname !== location.hostname \u0026\u0026\n          _url.href.match(/^(https:\\/\\/www.gstatic.com|https:\\/\\/apis.google.com\\/js\\/api.js|https:\\/\\/apis.google.com\\/_\\/)/);\n      }\n    ];\n  }\n  \u003c/script\u003e\n```\n\n### Hook Callback Function (customizable)\n\n```javascript\n  // Built-in Minimal Hook Callback Function without hooking properties (hook-property=false)\n  hook.__hook_except_properties__ = function __hook_except_properties__(f, thisArg, args, context, newTarget) {\n    return newTarget\n      ? Reflect.construct(f, args)\n      : thisArg\n        ? f.apply(thisArg, args)\n        : f(...args);\n  }\n```\n\n```javascript\n  // the global object\n  const _global = (new Function('return this'))();\n\n  // helper for strict mode\n  class StrictModeWrapper {\n    static ['#.'](o, p) { return o[p]; }\n    static ['#[]'](o, p) { return o[p]; }\n    static ['#*'](o) { return o; }\n    static ['#in'](o, p) { return p in o; }\n    static ['#()'](o, p, a) { return o[p](...a); }\n    static ['#p++'](o, p) { return o[p]++; }\n    static ['#++p'](o, p) { return ++o[p]; }\n    static ['#p--'](o, p) { return o[p]--; }\n    static ['#--p'](o, p) { return --o[p]; }\n    static ['#delete'](o, p) { return delete o[p]; }\n    static ['#='](o, p, v) { return o[p] = v; }\n    static ['#+='](o, p, v) { return o[p] += v; }\n    static ['#-='](o, p, v) { return o[p] -= v; }\n    static ['#*='](o, p, v) { return o[p] *= v; }\n    static ['#/='](o, p, v) { return o[p] /= v; }\n    static ['#%='](o, p, v) { return o[p] %= v; }\n    static ['#**='](o, p, v) { return o[p] **= v; }\n    static ['#\u003c\u003c='](o, p, v) { return o[p] \u003c\u003c= v; }\n    static ['#\u003e\u003e='](o, p, v) { return o[p] \u003e\u003e= v; }\n    static ['#\u003e\u003e\u003e='](o, p, v) { return o[p] \u003e\u003e\u003e= v; }\n    static ['#\u0026='](o, p, v) { return o[p] \u0026= v; }\n    static ['#^='](o, p, v) { return o[p] ^= v; }\n    static ['#|='](o, p, v) { return o[p] |= v; }\n    static ['#.='](o, p) { return { set ['='](v) { o[p] = v; }, get ['=']() { return o[p]; } }; }\n  }\n\n  // Built-in Minimal Hook Callback Function with hooking properties (hook-property=true) - default\n  function __hook__(f, thisArg, args, context, newTarget) {\n    let normalizedThisArg = thisArg;\n    if (newTarget === false) { // resolve the scope in 'with' statement body\n      let varName = args[0];\n      let __with__ = thisArg;\n      let scope = _global;\n      let _scope;\n      let i;\n      for (i = 0; i \u003c __with__.length; i++) {\n        _scope = __with__[i];\n        if (Reflect.has(_scope, varName)) {\n          if (_scope[Symbol.unscopables] \u0026\u0026 _scope[Symbol.unscopables][varName]) {\n            continue;\n          }\n          else {\n            scope = _scope;\n            break;\n          }\n        }\n      }\n      thisArg = normalizedThisArg = scope;\n    }\n    let result;\n    let args1 = args[1]; // for '()'\n    function * gen() {}\n    let GeneratorFunction = gen.constructor;\n    switch (f) {\n    case Function:\n      args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args);\n      break;\n    case GeneratorFunction:\n      args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args, true);\n      break;\n    case '()':\n    case '#()':\n      switch (thisArg) {\n      case Reflect:\n        switch (args[0]) {\n        case 'construct':\n          if (args[1]) {\n            switch (args[1][0]) {\n            case Function:\n              args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1])];\n              if (args[1][2]) {\n                args1.push(args[1][2]);\n              }\n              break;\n            default:\n              if (args[1][0].prototype instanceof Function) {\n                args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1], args[1][0].prototype instanceof GeneratorFunction)];\n                if (args[1][2]) {\n                  args1.push(args[1][2]);\n                }\n              }\n              break;\n            }\n          }\n          break;\n        case 'apply':\n          if (args[1]) {\n            switch (args[1][0]) {\n            case Function:\n              args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2])];\n              break;\n            case GeneratorFunction:\n              args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2], true)];\n              break;\n            default:\n              if (args[1][0].prototype instanceof Function) {\n                args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2], args[1][0].prototype instanceof GeneratorFunction)];\n              }\n              break;\n            }\n          }\n          break;\n        default:\n          break;\n        }\n        break;\n      case Function:\n        switch (args[0]) {\n        case 'apply':\n          args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1])];\n          break;\n        case 'call':\n          args1 = [args[1][0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1].slice(1))];\n          break;\n        default:\n          break;\n        }\n        break;\n      case GeneratorFunction:\n        switch (args[0]) {\n        case 'apply':\n          args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1], true)];\n          break;\n        case 'call':\n          args1 = [args[1][0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1].slice(1), true)];\n          break;\n        default:\n          break;\n        }\n        break;\n      default:\n        if (thisArg instanceof GeneratorFunction \u0026\u0026 args[0] === 'constructor') {\n          args1 = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1], true);\n        }\n        else if (thisArg instanceof Function \u0026\u0026 args[0] === 'constructor') {\n          args1 = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1]);\n        }\n        break;\n      }\n      break;\n    default:\n      if (typeof f === 'function') {\n        if (f.prototype instanceof Function \u0026\u0026 newTarget) {\n          args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args, f.prototype instanceof GeneratorFunction);\n        }\n        else if (newTarget === '') {\n          if (args[0] \u0026\u0026 Object.getPrototypeOf(args[0]) === Function) {\n            args = [ args[0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args.slice(1)) ];\n          }\n        }\n      }\n      break;\n    }\n    if (typeof f !== 'string') {\n      result = newTarget\n        ? Reflect.construct(f, args)\n        : thisArg\n          ? f.apply(thisArg, args)\n          : f(...args);\n    }\n    else {\n      // property access\n      switch (f) {\n      // getter\n      case '.':\n      case '[]':\n        result = thisArg[args[0]];\n        break;\n      // enumeration\n      case '*':\n        result = thisArg;\n        break;\n      // property existence\n      case 'in':\n        result = args[0] in thisArg;\n        break;\n      // funcation call\n      case '()':\n        result = thisArg[args[0]](...args1);\n        break;\n      // unary operators\n      case 'p++':\n        result = thisArg[args[0]]++;\n        break;\n      case '++p':\n        result = ++thisArg[args[0]];\n        break;\n      case 'p--':\n        result = thisArg[args[0]]--;\n        break;\n      case '--p':\n        result = --thisArg[args[0]];\n        break;\n      case 'delete':\n        result = delete thisArg[args[0]];\n        break;\n      // assignment operators\n      case '=':\n        result = thisArg[args[0]] = args[1];\n        break;\n      case '+=':\n        result = thisArg[args[0]] += args[1];\n        break;\n      case '-=':\n        result = thisArg[args[0]] -= args[1];\n        break;\n      case '*=':\n        result = thisArg[args[0]] *= args[1];\n        break;\n      case '/=':\n        result = thisArg[args[0]] /= args[1];\n        break;\n      case '%=':\n        result = thisArg[args[0]] %= args[1];\n        break;\n      case '**=':\n        result = thisArg[args[0]] **= args[1];\n        break;\n      case '\u003c\u003c=':\n        result = thisArg[args[0]] \u003c\u003c= args[1];\n        break;\n      case '\u003e\u003e=':\n        result = thisArg[args[0]] \u003e\u003e= args[1];\n        break;\n      case '\u003e\u003e\u003e=':\n        result = thisArg[args[0]] \u003e\u003e\u003e= args[1];\n        break;\n      case '\u0026=':\n        result = thisArg[args[0]] \u0026= args[1];\n        break;\n      case '^=':\n        result = thisArg[args[0]] ^= args[1];\n        break;\n      case '|=':\n        result = thisArg[args[0]] |= args[1];\n        break;\n      // LHS property access\n      case '.=':\n        result = { set ['='](v) { thisArg[args[0]] = v; }, get ['=']() { return thisArg[args[0]]; } };\n        break;\n      // strict mode operators prefixed with '#'\n      // getter\n      case '#.':\n      case '#[]':\n        result = StrictModeWrapper['#.'](thisArg, args[0]);\n        break;\n      // enumeration\n      case '#*':\n        result = StrictModeWrapper['#*'](thisArg);\n        break;\n      // property existence\n      case '#in':\n        result = StrictModeWrapper['#in'](thisArg, args[0]);\n        break;\n      // funcation call\n      case '#()':\n        result = StrictModeWrapper['#()'](thisArg, args[0], args1);\n        break;\n      // unary operators\n      case '#p++':\n        result = StrictModeWrapper['#p++'](thisArg, args[0]);\n        break;\n      case '#++p':\n        result = StrictModeWrapper['#++p'](thisArg, args[0]);\n        break;\n      case '#p--':\n        result = StrictModeWrapper['#p--'](thisArg, args[0]);\n        break;\n      case '#--p':\n        result = StrictModeWrapper['#--p'](thisArg, args[0]);\n        break;\n      case '#delete':\n        result = StrictModeWrapper['#delete'](thisArg, args[0]);\n        break;\n      // assignment operators\n      case '#=':\n        result = StrictModeWrapper['#='](thisArg, args[0], args[1]);\n        break;\n      case '#+=':\n        result = StrictModeWrapper['#+='](thisArg, args[0], args[1]);\n        break;\n      case '#-=':\n        result = StrictModeWrapper['#-='](thisArg, args[0], args[1]);\n        break;\n      case '#*=':\n        result = StrictModeWrapper['#*='](thisArg, args[0], args[1]);\n        break;\n      case '#/=':\n        result = StrictModeWrapper['#/='](thisArg, args[0], args[1]);\n        break;\n      case '#%=':\n        result = StrictModeWrapper['#%='](thisArg, args[0], args[1]);\n        break;\n      case '#**=':\n        result = StrictModeWrapper['#**='](thisArg, args[0], args[1]);\n        break;\n      case '#\u003c\u003c=':\n        result = StrictModeWrapper['#\u003c\u003c='](thisArg, args[0], args[1]);\n        break;\n      case '#\u003e\u003e=':\n        result = StrictModeWrapper['#\u003e\u003e='](thisArg, args[0], args[1]);\n        break;\n      case '#\u003e\u003e\u003e=':\n        result = StrictModeWrapper['#\u003e\u003e\u003e='](thisArg, args[0], args[1]);\n        break;\n      case '#\u0026=':\n        result = StrictModeWrapper['#\u0026='](thisArg, args[0], args[1]);\n        break;\n      case '#^=':\n        result = StrictModeWrapper['#^='](thisArg, args[0], args[1]);\n        break;\n      case '#|=':\n        result = StrictModeWrapper['#|='](thisArg, args[0], args[1]);\n        break;\n      // LHS property access\n      case '#.=':\n        result = StrictModeWrapper['#.='](thisArg, args[0]);\n        break;\n      // getter for super\n      case 's.':\n      case 's[]':\n        result = args[1](args[0]);\n        break;\n      // super method call\n      case 's()':\n        result = args[2](args[0]).apply(thisArg, args[1]);\n        break;\n      // unary operators for super\n      case 's++':\n      case '++s':\n      case 's--':\n      case '--s':\n        result = args[1].apply(thisArg, args);\n        break;\n      // assignment operators for super\n      case 's=':\n      case 's+=':\n      case 's-=':\n      case 's*=':\n      case 's/=':\n      case 's%=':\n      case 's**=':\n      case 's\u003c\u003c=':\n      case 's\u003e\u003e=':\n      case 's\u003e\u003e\u003e=':\n      case 's\u0026=':\n      case 's^=':\n      case 's|=':\n        result = args[2].apply(thisArg, args);\n        break;\n      // getter in 'with' statement body\n      case 'w.':\n      case 'w[]':\n        result = args[1]();\n        break;\n      // function call in 'with' statement body\n      case 'w()':\n        result = args[2](...args[1]);\n        break;\n      // constructor call in 'with' statement body\n      case 'wnew':\n        result = args[2](...args[1]);\n        break;\n      // unary operators in 'with' statement body\n      case 'w++':\n      case '++w':\n      case 'w--':\n      case '--w':\n        result = args[1]();\n        break;\n      // unary operators in 'with' statement body\n      case 'wtypeof':\n      case 'wdelete':\n        result = args[1]();\n        break;\n      // LHS value in 'with' statement body (__hook__('w.=', __with__, ['p', { set ['='](v) { p = v } } ], 'context', false)['='])\n      case 'w.=':\n        result = args[1];\n        break;\n      // assignment operators in 'with' statement body\n      case 'w=':\n      case 'w+=':\n      case 'w-=':\n      case 'w*=':\n      case 'w/=':\n      case 'w%=':\n      case 'w**=':\n      case 'w\u003c\u003c=':\n      case 'w\u003e\u003e=':\n      case 'w\u003e\u003e\u003e=':\n      case 'w\u0026=':\n      case 'w^=':\n      case 'w|=':\n        result = args[2](args[1]);\n        break;\n      // default (invalid operator)\n      default:\n        f(); // throw TypeError: f is not a function\n        result = null;\n        break;\n      }\n    }\n    return result;\n  }\n```\n\n```javascript\n  // Example Hook Callback Function with Primitive Access Control\n  hashContext = { 'hash': 'context', ... }; // Generated from hook.preprocess initialContext[0][1]\n  trustedContext = { 'context': /trustedModules/, ... }; // Access Policies\n\n  window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {\n    console.log('hook:', context, args);\n    if (!hashContext[context] ||\n        !trustedContext[hashContext[context]] ||\n        !(new Error('').stack.match(trustedContext[hashContext[context]]))) {\n      // plus check thisArg, args, etc.\n      throw new Error('Permission Denied');\n    }\n    return newTarget\n      ? Reflect.construct(f, args)\n      : thisArg\n        ? f.apply(thisArg, args)\n        : f(...args);\n  }\n```\n\n### Entry HTML with Service Worker\n\nIf hooking is performed run-time in Service Worker, the entry HTML page must be loaded via Service Worker\nso that no hook-targeted scripts are evaluated without hooking.\n\nTo achieve this, the static entry HTML has to be __Encoded__ at build time by `hook.serviceWorkerTransformers.encodeHTML(html)`.\n\n#### Hook CLI to encode the entry HTML\n\n```sh\n  # encode src/index.html to dist/index.html\n  hook --out dist/index.html src/index.html\n```\n\n#### Decoded/Original HTML (source code)\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"../thin-hook/hook.min.js?version=1\u0026no-hook=true\u0026hook-name=__hook__\u0026fallback-page=index-no-sw.html\u0026hook-property=false\u0026service-worker-ready=true\"\u003e\u003c/script\u003e\n    \u003c!-- Hook Callback Function witout hooking properties --\u003e\n    \u003cscript no-hook\u003e\n      window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {\n        ...\n        return newTarget\n          ? Reflect.construct(f, args)\n          : thisArg\n            ? f.apply(thisArg, args)\n            : f(...args);\n      }\n    \u003c/script\u003e\u003c!-- end of mandatory no-hook scripts --\u003e\n    \u003c!-- comment ---\u003e\n    \u003cscript src=\"...\"\u003e\u003c/script\u003e\n    ...\n\u003c/html\u003e\n```\n\n#### Encoded HTML (Service Worker converts it to Decoded HTML)\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cscript src=\"../thin-hook/hook.min.js?version=1\u0026no-hook=true\u0026hook-name=__hook__\u0026fallback-page=index-no-sw.html\u0026hook-property=false\u0026service-worker-ready=false\"\u003e\u003c/script\u003e\u003c/head\u003e\u003c/html\u003e\n    \u003c!-- Hook Callback Function without hooking properties --\u003e\n    \u003cscript no-hook\u003e\n      window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {\n        ...\n        return newTarget\n          ? Reflect.construct(f, args)\n          : thisArg\n            ? f.apply(thisArg, args)\n            : f(...args);\n      }\n    \u003c/script\u003e\u003c!--\u003cC!-- end of mandatory no-hook scripts --C\u003e\n    \u003cC!-- comment --C\u003e\n    \u003cscript src=\"...\"\u003e\u003c/script\u003e\n    ...\n\u003c/html\u003e--\u003e\n```\n\n- `\u003c/head\u003e\u003c/html\u003e` is inserted between the first `hook.min.js` script and the second no-hook script, which looks strange but is required for correct execution of no-hook scripts.\n  - If `\u003c/head\u003e\u003c/html\u003e` is inserted at the end of mandatory no-hook scripts according to the normal HTML format, the page encounters the unexpected \"hook is not defined\" error, whose root cause is under investigation.\n\n## Supported Syntax\n\n- Functions\n- Object Shorthand Methods (`{ m() {} }`)\n- ES6 Classes (`constructor`, `super`, `this`, `new`)\n- ES6 Modules (`import`, `export`);\n- Expressions in Template Literals(`` `${(v =\u003e v * v)(x)}` ``)\n- Generator Functions (`function *g() { yield X }`)\n- Arrow Functions (`a =\u003e a`, `a =\u003e { return a; }`, `a =\u003e ({ p: a })`)\n- Async Functions (`async function f() {}`, `async method() {}`, `async () =\u003e {}`)\n- Default Parameters for Functions/Methods/Arrow Functions\n- Default Parameters with Destructuring (`function f([ a = 1 ], { b = 2, x: c = 3 }) {}`)\n- Property Accessors (`o.p`, `o['p']`, `o.p()`)\n\n## Install\n\n### Browsers\n\n```sh\n  bower install --save thin-hook\n```\n\n### NodeJS\n\n```sh\n  npm install --save thin-hook\n```\n\n## Import\n\n### Browsers\n\n```html\n  \u003c!-- browserified along with espree and escodegen; minified --\u003e\n  \u003cscript src=\"path/to/bower_components/thin-hook/hook.min.js\"\u003e\u003c/script\u003e\n```\n\n### NodeJS\n\n```javascript\n  const hook = require('thin-hook/hook.js');\n```\n\n## API (Tentative)\n\n- `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)`\n  - `code`: input JavaScript as string\n  - `hookName`: name of hook callback function\n  - `initialContext`: typically `[ ['script.js', {}] ]`\n  - `contextGeneratorName`: function property name in `hook.contextGenerators`\n    - argument `astPath = [ ['script.js', {}], ['root', rootAst], ['body', bodyAst], ..., [0, FunctionExpressionAst] ]`\n  - `metaHooking`: Enable meta hooking (run-time hooking of metaprogramming) if true\n  - `hookProperty`: Enable hooking of object property accessors and new operators if true\n  - `sourceMap`: Source map parameter in an object. `{ pathname: 'path/to/script_source.js'}` Default: null\n  - `asynchronous`: Return a Promise if true. Default: false\n  - `compact`: Generate compact code if true. Default: false\n    - Note: `sourceMap` is disabled when `compact` is true\n  - `hookGlobal`: Hook global variable access. Must be enabled with `hookProperty`. Default: true\n  - `hookPrefix`: Prefix for `hook.global()._p_GlobalVariable` proxy accessors. Default: `_p_`\n    - Note: `hook.global()` return the global object with `get/set` accessors for the prefixed name\n  - `initialScope`: Initial scope object (`{ vname: true, ... }`) for hooked eval scripts. Default: null\n- `$hook$`: `$hook$ === hook`. Alias of `hook` in hooked scripts\n- `hook.hookHtml(html: string, hookName, url, cors, contextGenerator, contextGeneratorScripts, isDecoded, metaHooking = true, scriptOffset = 0, _hookProperty = true, asynchronous = false)`\n- `hook.__hook__(f: function or string, thisArg: object, args: Array, context: string, newTarget: new.target meta property)`\n  - minimal hook callback function with property hooking\n  - `f`:\n    - `function`: target function to hook\n    - `string`: property operation to hook\n      - `.`: get property (`o.prop`)\n      - `*`: iterate over (`for (p in o)`, `for (p of o)`)\n      - `in`: property existence (`'p' in o`)\n      - `()`: function call (`o.func()`)\n      - `=`, `+=`, ...: assignment operation (`o.prop = value`)\n      - `p++`, `++p`, `p--`, `--p`: postfixed/prefixed increment/decrement operation (`o.prop++`)\n      - `delete`: delete operation (`delete o.prop`)\n      - `s.`: get property of super (`super.prop`)\n      - `s()`: call super method (`super.method()`)\n      - `s=`, `s+=`, ...: assignment operation for super (`super.prop = value`)\n      - `s++`, `++s`, `s--`, `--s`: postfixed/prefixed increment/decrement operation for super (`super.prop++`)\n      - `w.`, `w=`, `w()`, `w++`, ...: operations on variables in within `with` statements\n  - `thisArg`: `this` object for the function or the operation\n  - `args`:\n    - arguments for the function\n    - `[ property ]` for property access operations\n    - `[ property, value ]` for property assignment operations\n    - `[ property, [...args] ]` for function call operations\n  - `context`: context in the script\n  - `newTarget`: `new.target` meta property for constructor calls;\n    - `true` for new calls\n    - Falsy values for non-`new` operations for faster detection of the operations\n      - `false` for `with` statement calls\n      - `0` for function calls\n      - `undefined` for other calls\n- `hook.__hook_except_properties__(f, thisArg, args, context, newTarget)`\n  - minimal hook callback function without property hooking\n- `hook.hookCallbackCompatibilityTest(__hook__ = window[hookName], throwError = true, checkTypeError = true)`\n  - run-time test suite for hook callback function\n  - Usage: `window.__hook__ = function __hook__ (...) {}; hook.hookCallbackCompatibilityTest();`\n  - An error is thrown on compatibility test failure.\n  - `false` is returned on a test failure if `throwError = false`\n  - tests on non-callable object's function call are skipped if `checkTypeError = false`\n- `hook.contextGenerators`: object. Context Generator Functions\n  - `null()`: context as `''`\n  - `astPath(astPath: Array)`: context as `'script.js,[root]Program,body,astType,...'`\n  - `method(astPath: Array)`: context as `'script.js,Class,Method'` with caching, including computed method variable name\n  - `cachedMethod(astPath: Array)`: alias for `method`\n  - `cachedMethodDebug(astPath: Array)`: context as `'script.js,Class,Method'`, comparing contexts with those by \"oldMethod\" in console.warn() messages\n  - `oldMethod(astPath: Array)`: context as `'script.js,Class,Method'` for compatibility\n  - custom context generator function has to be added to this object with its unique contextGeneratorName\n- `hook.$(symbolToContext = __hook__, contexts)`: context symbol generator function used in hooked scripts to generate symbols corresponding to given contexts\n  - Example call inserted at the beginning of a hooked script: `const __context_mapper__ = $hook$.$(__hook__, [ 'examples/example2.js,C', ... ]);`\n  - `__context_mapper__`: `Array` of symbol contexts\n    - In a hooked script, `__context_mapper__` is actually `__ + hex(sha256(topContextOfScript + code)) + __`\n      - Note: Due to this specification, **the same script in the same URL cannot be loaded to a single document multiple times**\n    - `__context_mapper__[N]`: the symbol context corresponding to the string context `contexts[N]`\n    - `__hook__[__context_mapper__[N]]` is set as `contexts[N]` so that `__hook__` can convert symbol contexts to their corresponding string contexts\n- Hooked Native APIs: Automatically applied in `hook()` preprocessing\n  - `hook.global(hookCallback: function = hookName, context: string, name: string, type: string)._p_name`: hooked global variable accessor when `hookGlobal` is true\n    - `type`: one of `'var', 'function', 'let', 'const', 'class', 'get', 'set', 'delete', 'typeof'`\n  - `hook.Function(hookName, initialContext: Array = [['Function', {}]], contextGeneratorName)`: hooked Function constructor for use in hook callback function `__hook__`\n    - Usage: `(new (hook.Function('__hook__', [['window,Function', {}]], 'method'))('return function f() {}'))()`\n    - Notes:\n      - Avoid replacing the native API `window.Function` for better transparency (now commented out in the `demo/hook-native-api.js`)\n      - **NOT automatically applied in the hooking**\n      - Applied in the hook callback function (`__hook__`) instead\n  - `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__`\n    - Usage: `hook.FunctionArguments('__hook__', [['window,Function', {}]], 'method', ['return function f() {}'])`\n    - Returns hooked `args` in a cloned `Array`\n  - `hook.eval(hookName, initialContext: Array = [['eval', {}]], contextGeneratorName)`: hooked eval function\n    - Usage: `hook.eval('__hook__', [['eval', {}]], 'method'))('1 + 2', (script, eval) =\u003e eval(script))`\n    - 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) =\u003e eval(script)` is defined in the local scope and specifed as the second argument of each `eval()` call\n  - `hook.setTimeout(hookName, initialContext: Array = [['setTimeout', {}]], contextGeneratorName)`: hooked setTimeout function\n    - Note: Not automatically applied if the first argument is an (arrow) function expression\n  - `hook.setInterval(hookName, initialContext: Array = [['setInterval', {}]], contextGeneratorName)`: hooked setInterval function\n    - Note: Not automatically applied if the first argument is an (arrow) function expression\n  - `hook.Node(hookName, initialContext: Array = [['Node', {}]], contextGeneratorName)`: hook `textContent` property\n    - `set textContent`: hooked with context 'ClassName,set textContent'\n  - `hook.Element(hookName, initialContext: Array = [['Element', {}]], contextGeneratorName)`: hook `setAttribute` function\n    - `setAttribute('onXX', '{script in attribute}')`: Script in onXX handler attribute is hooked\n    - `setAttribute('href', 'javascript:{script in URL}')`: Script in URL `\"javascript:{script in URL}\"` is hooked\n  - `hook.HTMLScriptElement(hookName, initialContext: Array = [['HTMLScriptElement', {}]], contextGeneratorName)`: HTMLScriptElement with hooked properties\n    - 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.\n    - `set textContent`: Script in `textContent` is hooked if `type` is a JavaScript MIME type. `Node.textContent` is hooked as well.\n      - Note: Scripts set by `innerHTML`/`outerHTML`/`text` properties are NOT executed, while `text` should be executed according to the standards.\n    - `set type`: Script in `this.textContent` is hooked if `type` is a JavaScript MIME type.\n    - `setAttribute('type', mimeType)`: Script in `this.textContent` is hooked if `mimeType` is a JavaScript MIME type. `Element.setAttribute` is hooked as well.\n  - `hook.HTMLAnchorElement(hookName, initialContext: Array = [['HTMLAnchorElement', {}]]), contextGeneratorName)`: HTMLAnchorElement with hooked href property\n    - `set href`: Script in URL `\"javascript:{script in URL}\"` is hooked\n  - `hook.HTMLAreaElement(hookName, initialContext: Array = [['HTMLAreaElement', {}]]), contextGeneratorName)`: HTMLAreaElement with hooked href property\n    - `set href`: Script in URL `\"javascript:{script in URL}\"` is hooked\n  - `hook.Document(hookName, initialContext: Array = [['Document', {}]], contextGeneratorName)`: hook `write` function\n    - `write('\u003csc' + 'ript\u003e{script in string}\u003c/sc' + 'ript\u003e')`: Script in HTML fragment is hooked\n  - `hook.with(scope: Object, ...scopes: Array of Object)`: Hook `with` statement scope object\n    - `with (hook.with(obj, { v1: true, v2: true, ...})) {}`\n  - `hook.importScripts()`: return hooked `importScripts` function for Workers, invalidating extensions other than `.js` and `.mjs`\n    - Note: No arguments to pass\n- `hook.hook(target: Class, ...)`: hook platform global object with `target`\n  - Usage: `['Function','setTimeout','setInterval',...].forEach(name =\u003e hook.hook(hook.Function('__hook__', [[name, {}]], 'method'))`\n- `hook.serviceWorkerHandlers`: Service Worker event handlers\n  - `install`: 'install' event handler. Set version from the `version` parameter\n  - `activate`: 'activate' event handler. Clear caches of old versions.\n  - `message`: 'message' event handler.\n    - **INTERNAL** `'channel'` message: Transfer MessageChannel port objects for hook workers from the main document to the Service Worker at initialization\n    - **INTERNAL** `'unload'` message: Trigger unloading of hook workers\n    - **INTERNAL** `'coverage'` message: Transfer `__coverage__` instanbul coverage object for the Service Worker to the main document to collect code coverage in `test/hook.min.js`\n    - `['plugin', 'pluginId', ...params ]` message: Transfer a message to the target plugin identified by `'pluginId'`. The target plugin must add its own event listener to handle the message.\n      - `['plugin', 'pluginId:enqueue', ...params ]`: When the `pluginId` ends with `:enqueue`, events with posted messages are enqueued to `hook.parameters.messageQueues['pluginId:enqueue'] = []` even before plugins are loaded into the Service Worker\n        - Each enqueued message is immediately responded via `event.ports[0].postMessage()` with a dummy response message generated by cloning the posted message and appending `':enqueued'` such as `['plugin', 'pluginId:enqueue', ...params, ':enqueued' ]`\n        - The target plugin must dequeue the enqueued events and append `':dequeued'` to the queue to stop further enqueueing. For example, the queue `[]` changes as follows:\n          - An event is enqueued: `[ event1 ]`\n          - The plugin append `':dequeued'`: `[ event1, ':dequeued' ]`\n          - The plugin dequeues and processes the event(s): `[ ':dequeued' ]`\n        - Enqueued messages are likely to be one-way messages as the main document is about to reload itself\n        - `hook.parameters.messageQueues['pluginId:enqueue']` may NOT exist when the plugin is loaded. So the plugin must create its own queue if it has not been created.\n  - `fetch`: 'fetch' event handler. Cache hooked JavaScripts and HTMLs except for the main page loading `hook.min.js`\n    - `\u003cscript src=\"thin-hook/hook.min.js?version=1\u0026sw-root=/\u0026no-hook=true\u0026hook-name=__hook__\u0026discard-hook-errors=true\u0026fallback-page=index-no-sw.html\u0026hook-property=true\u0026service-worker-ready=true\"\u003e\u003c/script\u003e`: arguments from the page\n      - `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.\n      - `sw-root`: optional. Set Service Worker scope\n      - `hook-name`: default `__hook__`. hook callback function name\n      - `context-generator-name`: default `method`. context generator callback function name\n      - `discard-hook-errors`: `true` if errors in hooking are ignored and the original contents are provided. Default: `true`\n      - `fallback-page`: fallback page to land if Service Worker is not available in the browser\n      - `no-hook-authorization`: Optional. CSV of no-hook authorization tickets for no-hook scripts. Typically for ticket of no-hook authorization script itself.\n        - The values are stored in `hook.parameters.noHookAuthorizationPreValidated` object in Service Worker\n        - Add the value `log-no-hook-authorization` to log authorization in console\n        - Note: `no-hook-authorization` must not exist in learning mode with `hook.parameters.noHookAuthorization['*'] === true`\n          - Steps to update authorized no-hook scripts:\n            - 1. Let no-hook be \"learning mode\" by truthy `hook.parameters.noHookAuthorization['*']`\n            - 2. Remove (or temporarily rename) `no-hook-authorization` parameter from hook.min.js\n            - 3. Update no-hook script(s)\n            - 4. Clear Service Worker(s)\n            - 5. Update `version` parameter for hook.min.js\n            - 6. Check \"Preserve Logs\" option in debugger console\n            - 7. Reload the page(s) with no-hook script(s)\n            - 8. Copy and Paste values of hook.parameters.noHookAuthorizationPassed from both browser document and Service Worker to no-hook authorization script\n            - 9. Disable \"learning mode\"\n            - 10. Enable (or revive) `no-hook-authorization` parameter for hook.min.js with a dummy value\n            - 11. Clear Service Worker(s)\n            - 12. Update `version` parameter for hook.min.js\n            - 13. Reload the page(s) with no-hook scripts(s)\n            - 14. Copy and Paste the ticket for the no-hook authorization script into the `no-hook-authorization` parameter\n            - 15. Update `version` parameter for hook.min.js\n            - 16. Clear Service Worker(s)\n            - 17. Reload the page(s) with no-hook script(s)\n            - 18. Check if there are no unauthorized no-hook scripts\n      - `hook-property`: `hookProperty` parameter. `true` if property accessors are hooked. The value affects the default value of the `hookProperty` parameter for `hook()`\n      - `hook-global`: `hookGlobal` parameter. `true` if global variables are hooked. The value affects the default value of the `hookGlobal` parameter for `hook()`\n      - `hook-prefix`: `hookPrefix` parameter. Prefix accessor names of `hook.global()._p_GlobalVariableName` with the value. Default: `_p_`\n      - `compact`: `compact` parameter. Generate compact code if `true`. The value affects the default value of the `compact` parameter for `hook()`\n      - `service-worker-ready`: `true` if the entry HTML page is decoded; `false` if encoded. This parameter must be at the end of the URL\n    - `\u003cscript src=\"script.js?no-hook=true\"\u003e\u003c/script\u003e`: skip hooking for the source script\n    - `\u003cscript no-hook\u003e...\u003c/script\u003e`: skip hooking for the embedded script\n    - `\u003cscript context-generator\u003e`: register a custom context generator for both Service Worker and browser document\n      - `\u003cscript context-generator no-hook\u003ehook.contextGenerators.custom = function (astPath) {...}\u003c/script\u003e`: embedded script\n      - `\u003cscript context-generator src=\"custom-context-generator.js?no-hook=true\"\u003e\u003c/script\u003e`: with src URL\n      - Valid only in the main entry document with `hook.min.js` for Service Worker\n      - Must be runnable in both Service Worker and browser document\n      - Defined variables for context generator scripts in Service Worker\n        - `version` variable: cache name as a string `version_{version number}`\n          - Note: In Service Worker, `'version_' + new URL(location.href).searchParams.get('version')` might be incorrect since Service Worker for the old version before version upgrading might still be running for the new version. In contrast, `'version_' + new URL(document.querySelector('script').src).searchParams.get('version')` in the main document is always up-to-date.\n      - Extensions other than context generators:\n        - Set Service Worker parameters:\n          - `hook.parameters.cors = [ 'cors_url_1', 'cors_url_2', ... ]`: specify CORS script URLs\n          - `hook.parameters.cors = [ (url) =\u003e url.match(/cors_url_pattern/), ... ]`: specify CORS script URL detector function(s)\n          - `hook.parameters.opaque = [ 'opaque_url_1', 'opaque_url_2', ... ]`: specify authorized opaque URLs\n          - `hook.parameters.opaque = [ (url) =\u003e url.match(/opaque_url_pattern/), ... ]`: specify authorized opaque URL detector function(s)\n        - Set `no-hook` Authorization Tickets:\n          - `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__\n          - `hook.parameters.noHookAuthorization = { '*': true }`: learning mode to detect authorization tickets\n        - Specify URL patterns for `no-hook` scripts:\n          - `hook.parameters.noHook = [ 'no_hook_url_1', 'no_hook_url_2', ... ]`: specify `no-hook` script URLs\n          - `hook.parameters.noHook = [ (url: URL) =\u003e !!url.href.match(/{no-hook URL pattern}/), ... ]`: specify `no-hook` script URL detector function(s)\n        - Specify URL patterns for source map target scripts:\n          - `hook.parameters.sourceMap = [ 'source_map_target_url_1', 'source_map_target_url_2', ... ]`: specify source map target script URLs\n          - `hook.parameters.sourceMap = [ (url: URL) =\u003e !!url.href.match(/{source map target URL pattern}/), ... ]`: specify source map target script URL detector function(s)\n        - Specify URL for hook worker script:\n          - `hook.parameters.hookWorker = 'hook-worker.js?no-hook=true'`: specify hook worker script URL\n        - Register Custom Event Handler:\n          - `if (typeof self === 'object' \u0026\u0026 self instanceof 'ServiceWorkerGlobalScope') { self.addEventListener('{event_type}', function handler(event) {...})}`\n        - URL for the entry page\n          - `hook.parameters.baseURI`: Set in `demo/bootstrap.js`\n        - Empty Document URL\n          - `hook.parameters.emptyDocumentUrl = new URL('./empty-document.html', baseURI);`: Set in `demo/bootstrap.js`. \n          - `\u003ciframe src=\"empty-document.html?url=https://host/path.html,iframe\"\u003e` to specify context in iframe document\n        - Bootstrap Script Tag\n          - `hook.parameters.bootstrap = \"\u003cscript\u003eframeElement.dispatchEvent(new Event('srcdoc-load'))\u003c/script\u003e\";`: Set in `demo/bootstrap.js`\n          - Append to the hooked `srcdoc` to dispatch `srcdoc-load` event to `onload` handler\n        - Onload Wrapper Script\n          - `hook.parameters.onloadWrapper = \"event.target.addEventListener('srcdoc-load', () =\u003e { $onload$ })\";`: Set in `demo/bootstrap.js`\n          - Receive `srcdoc-load` event and trigger the original `onload` script\n            - Note: `addEventListener('load', handler)` is currently called BEFORE the document from `srcdoc` is loaded and `srcdoc-load` event is fired.\n        - Virtual Blob URL (disabled by default)\n          - `hook.parameters.virtualBlobUrlTargetType = new Map([['text/html', 'file.html'],['text/javascript', 'file.js'],['image/svg+xml', 'file.svg']]);`: Set in `demo/bootstrap.js` to specify target MIME types and their corresponding virtual Blob URL file names\n          - `hook.parameters.virtualBlobBaseUrl = null;//new URL('blob/', hook.parameters.baseURI).href;`: Set in `demo/bootstrap.js` to specify the base URL for virtual Blob URLs\n          - Convert Blob URLs to Virtual Blob URLs in https so that they can be preprocessed in Service Worker and set in attributes\n            - Original Blob URL: `blob:https://origin.site/abcd...1234` for `text/html` Blob object\n            - Virtual Blob URL: `https://origin.site/entry/blob/file.html?bloburl=blob:https://origin.site/abcd...1234`\n          - If these parameters are not configured (which is default), no conversion will be performed on Blob URLs\n        - Flag to block `\u003cembed\u003e` and `\u003cobject\u003e` elements\n          - `hook.parameters.hangUpOnEmbedAndObjectElement = false;`: Set in `demo/bootstrap.js`\n          - If the flag is set as `true`, the application hangs up on encountering activities by `\u003cobject\u003e` and `\u003cembed\u003e` elements\n            - To use this flag, `hook.parameters.mutationObserver` and `hook.parameters.mutationObserverConfig` must be set in `demo/hook-callback.js`\n        - Empty SVG to load the target URL\n          - `hook.parameters.emptySvg = '\u003c?xml version=\"1.0\"?\u003e\u003c!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"\u003e\u003csvg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" width=\"1px\" height=\"1px\"\u003e\u003cscript\u003elocation = \"$location$\";\u003c/script\u003e\u003c/svg\u003e';`\n            - If `hook.parameters.hangUpOnEmbedAndObjectElement = true`, the SVG loads `about:blank`\n        - Bootstrap Scripts for SVG\n          - `hook.parameters.bootstrapSvgScripts = '\u003cscript xlink:href=\"URL?params\"\u003e\u003c/script\u003e...'`\n        - Flag to omit superfluous closing tags\n          - `hook.parameters.omitSuperfluousClosingHtmlTags = true`: `true` to omit superfluous closing tags; Set in `demo/bootstrap.js`\n            - `false` to be compatible with old versions\n            - In SVG, tag names and attribute names are case-sensitive. If the flag is `false`, SVG images with case-sensitive tags and attributes can be broken.\n            - If the flag is set as `true`, the output should have minimal required changes from the original HTML or SVG\n            - Note on Side-Effects: Policy contexts for inline scripts can contain `/path/script.js,script@{pos}`, which can vary with this flag set as `true`\n        - Check Request callback on Fetch at Service Worker\n          - `hook.parameters.checkRequest = async function (event, response, cache) { /* check request */ return response ; }`: `response` - cached response if exists; See `demo/disable-devtools.js`\n        - Check Response callback on Fetch at Service Worker\n          - `hook.parameters.checkResponse = async function (event, request, response, cache) { /* check response */ return response ; }`: `response` - just fetched response; Not called if a cache response exists\n        - List of Asynchronous Tasks before Service Worker registration\n          - `hook.parameters.preServiceWorkerTasks`: The first task is checked after `DOMContentLoaded` event; Therefore, the first task `Promise` must be pushed before `DOMContentLoaded` event \n            - If the last task resolves to a constant string `\"skipServiceWorkerRegistration\"`, the default Service Worker registration processes are skipped and the last task takes the responsiblity of the Service Worker registration and reloading the entry page. Even after the Service Worker registration completed and the page is reloaded, this `\"skipServiceWorkerRegistration\"` value is effective for the task so that `hook.min.js` can complete remaining tasks such as starting the ping service and the hook workers.\n            - 3 Acceptable Data Types\n              - `Promise`: A single `Promise` object; Equivalent to `[ Promise ]`;\n              - `[ Promise, Promise, ...]`: `Array` of `Promise` objects\n              - `Async Iterable`: `Async Iterator` implementing `tasks[Symbol.asyncIterator]` protocol\n        - Callback on Errors from `hook.parameters.preServiceWorkerTasks`\n          - `hook.parameters.onPreServiceWorkerTasksError = async function onError(exception) {}`: Asynchronous function to handle the exception\n            - Default: `window.location = 'about:blank';`\n        - Callback to Decode Entry HTML in a plugin\n          - `hook.parameters.decodeEntryHtml = async function decodeEntryHtml(event, request, response, cache, original, decoded)`\n            - `event: FetchEvent`: Event for the fetch request\n            - `request: Request`: Request object that fetched the content\n            - `response: Response`: Response object of the fetch\n            - `cache: Cache`: Cache object for the current version\n            - `original: String` : original entry page HTML\n            - `decoded: String` : `= hook.serviceWorkerTransformers.decodeHtml(original)`\n            - return value: `String` decoded entry page HTML to respond to the document\n              - The function can just return `original` or `decoded` while it can also modify the content depending on the situation.\n        - Optional headers to include in cache response headers\n          - `hook.parameters.significantHeaders = { \"Header-Name\": true }`\n        - Additional Cacheable Content-Types\n          - `hook.parameters.cacheableContentTypes = { \"text/css\": true, \"image/png\": true, ... }`\n            - Note: `text/html`, `text/javascript`, `image/svg+xml` must not be included here\n        - Callback to Validate Cacheable URL\n          - `hook.parameters.validateCacheableUrl = function (url, contentType)`\n            - `url: String`: target URL to validate\n            - `contentType: String`: normalized Content-Type without charset\n            - return a truthy value if `url` with `contentType` is cacheable\n            - If the callback function is undefined, any `contentType` values within `hook.parameters.cacheableContentTypes` are cacheable\n        - Root of Application Path\n          - `hook.parameters.appPathRoot = '/';` - The app assets are under `location.origin + hook.parameters.appPathRoot`\n        - Script Hashes\n          - `hook.parameters.scriptHashes = { \"SHA256(authorized inline script)\": \"context\", ... }` - List of hashes for authorized inline scripts\n        - Integrity\n          - `hook.parameters.integrity = { \"URL path\": \"base64(SHA256(response data))\", ... }` - List of integrity for static contents\n        - MutationObserver\n          - `hook.parameters.mutationObserver = new MutationObserver(observerCallback);` - `MutationObserver` object set in `demo/hook-callback.js`\n          - `hook.parameters.mutationObserverConfig = { childList: true, subtree: true, attributes: true, attributeOldValue: true, characterData: true, characterDataOldValue: true, };` - Configuration options for `hook.parameters.mutationObserver.observe(options)` set in `demo/hook-callback.js`\n            - Note: They are used in the wrapped `Node.attachShadow()` to track mutations in every shadow DOM as well as for all document objects of windows and frames\n        - Tracker Callback\n          - `hook.parameters.innerHTMLTracker = function (node, value, processed) {}`: Set in `demo/hook-callback.js` for mutation detection\n          - Track each `Element.innerHTML` operation before performing it\n        - Import Maps\n          - `hook.parameters.importMapsJson = \"{ JSON string for Import Maps }\"`: Optional import maps object in JSON string.\n          - `hook.parameters.importMapper(specifier, scriptURL)`: Wrapper function for import maps. Resolve module `specifier` from `scriptURL`\n            - Resolution of bare specifiers for ES modules can be disabled by setting this function as `hook.parameters.importMapper = null`\n          - `hook.parameters.moduleDependencies = {}`: Optional object to dump module dependencies for hooked modules\n    - register as Service Worker\n      - `Service-Worker-Allowed` HTTP response header must have an appropriate scope for the target application\n    - `cors=true` parameter: CORS script, e.g., `\u003cscript src=\"https://cross.origin.host/path/script.js?cors=true\"\u003e\u003c/script\u003e`\n- `hook.serviceWorkerTransformers`:\n  - `encodeHtml(html: string)`: encode HTML for Service Worker\n    - `\u003c!-- end of mandatory no-hook scripts --\u003e`: insert this exact marker as a comment so that all mandatory no-hook scripts before the marker in the HTML of the entry document can be executed even at the first load without Service Worker\n      - Note: `no-hook-authorization` hashes are NOT effective at the first load\n  - `decodeHtml(html: string)`: decode encoded HTML for Service Worker\n- `hook.hookWorkerHandler(event)`: onmessage handler for Hook Workers\n  - Usage: `onmessage = hook.hookWorkerHandler` in Hook Worker script\n- `hook.registerServiceWorker(fallbackUrl: string = './index-no-service-worker.html', reloadTimeout: number = 500, inactiveReloadTimeout: number = 1000)`:\n  - Automatically called on loading `hook.min.js` on browsers\n  - `fallbackUrl`: fallback URL for browsers without Service Worker\n  - `reloadTimeout`: default: 500 (ms). Timeout to reload the page when no Service Worker is detected\n  - `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.\n- `utils`: Utilities\n  - `createHash`: Synchronous SHA hash generator collections from [sha.js](https://github.com/crypto-browserify/sha.js)\n  - `HTMLParser`: HTML parser from [htmlparser2](https://www.npmjs.com/package/htmlparser2)\n  - `importMaps`: Forked reference implementation of [Import maps](https://github.com/t2ym/import-maps/tree/browserify/reference-implementation/lib)\n    - `parseFromString(importMapsJsonString, baseURL)`: Parser of import maps JSON string at `baseURL`. Return `parsedImportMap` object for `resolve()`\n    - `resolve(specifier, parsedImportMap, scriptURL)`: Resolver of `specifier` for `scriptURL` based on `parsedImportMap`\n\n## Plugins\n\n- Plugins are no-hook scripts for enhancements\n  - Currently, they are configured for the demo application under `demo/`, but fully customizable for any target applications\n\n### `\u003cscript context-generator src=\"no-hook-authorization.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Configurations\n  - `hook.parameters.noHookAuthorization = { \"hex sha256 digest for no-hook script\": true, ... }`\n    - Hex sha256 digests have to be updated in the build process\n      - See `update-no-hook-authorization` gulp task\n    - Hex sha256 digest of the `no-hook-authorization.js` script itself has to be set as a parameter for `hook.min.js`\n      - `\u003cscript src=\"../../thin-hook/hook.min.js?version=496\u0026no-hook-authorization=6a83335a7630118516213f52715a24520efc7030b3562291e92a06482894b95e\u0026service-worker-ready=false\"\u003e\u003c/script\u003e`\n      - See `update-no-hook-authorization-in-html` gulp task\n  - `hook.parameters.sourceMap = [...]`\n  - `hook.parameters.hookWorker = 'hook-worker.js?no-hook=true';`\n\n### `\u003cscript context-generator src=\"integrity.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Features\n  - Check integrity of the browser agent\n  - Check integrity of the loaded scripts\n  - Establish and update secure connection to `integrityService.js`\n  - Check integrity of requests and responses\n  - Encrypt request body data\n  - Decrypt response body data\n  - Check integrity of Service Worker cache contents by appending and verifying `x-cache-*` headers\n  - TBD\n- Configurations\n  - TBD\n\n### `\u003cscript context-generator src=\"disable-devtools.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Features\n  - Force redirection to `about:blank` when the user tries to open Developer Tools\n  - Force redirection to `about:blank` when the user tries to inspect a source code of the pages\n- Configurations\n  - `const devtoolsDisabled = true`: Use `false` and rebuild with `gulp demo` to enable Dev Tools\n    - Configurable at `targetConfig.mode.devtoolsDisabled` in `demo-config/config.js`\n\n### `\u003cscript context-generator src=\"context-generator.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Configurations\n  - `hook.contextGenerators.hash`: an example custom context generator (not used for demo)\n  - `hook.contextGenerators.method2`: an example custom context generator (not used for demo)\n  - `Object.freeze(hook.contextGenerators)`\n\n### `\u003cscript context-generator src='bootstrap.js?no-hook=true'\u003e\u003c/script\u003e`\n\n- Configurations\n  - `hook.parameters.emptyDocumentUrl`\n  - `hook.parameters.bootstrap`\n  - `hook.parameters.onloadWrapper`\n  - `hook.parameters.virtualBlobUrlTargetType`\n  - `hook.parameters.virtualBlobBaseUrl`\n  - `hook.parameters.hangUpOnEmbedAndObjectElement`\n  - `hook.parameters.emptySvg`\n  - `hook.parameters.bootstrapSvgScripts`\n  - `hook.parameters.noHookAuthorizationParameter`: Value of `hook.min.js?no-hook-authorization` parameter used in `hook-callback.js`\n  - `hook.parameters.noHookAuthorizationFailed = {}`\n  - `hook.parameters.noHookAuthorizationPassed = {}`\n\n### `\u003cscript context-generator no-hook\u003ehook.parameters.* ...\u003c/script\u003e`\n\n- Configurations\n  - `hook.parameters.cors`\n  - `hook.parameters.opaque`\n  - `hook.parameters.worker` (Ineffective and unused for now)\n\n### `\u003cscript context-generator src=\"cache-bundle.js?no-hook=true\u0026authorization=...\"\u003e\u003c/script\u003e`\n\n- Features\n  - Fetch `cache-bundle.json` and store the contents into `caches`\n    - Format: `{ \"version\": \"version_XXX\", \"same origin URL path (absolute)\": \"text data\", ..., \"absolute URL\": \"text data\", ... }`\n    - Basic MIME types:\n      - `.js`: `application/json`\n      - `.html`: `text/html`\n      - `.json`: `application/json`\n      - `.svg`: `image/svg+xml`\n    - Extended Metadata Format: See `demo/cache-bundle.json`\n      - key: `\"url?param=2\": Object`\n        - property: `\"Location\": \"url?param=1\"` - link to the other content to eliminate redundant identical body data for multiple URLs\n          - Note: If Non-dataURI `\"Location\"` exists, other metadata entries are ignored\n        - property: `\"Location\": \"data:image/jpeg;base64,...\"` - encoded body data for non-textual contents\n          - Note: `\"Location\"` appears only once in a metadata object, of course\n        - property: `\"Content-Type\": \"text/xml\"` - MIME type\n        - property: `\"body\": \"body in string\"` - content body\n        - property: `\"Other-Headers\": \"header value\"` - additional significant HTTP headers specified in `hook.parameters.significantHeaders`\n  - Generate `cache-bundle.json` from `caches` and upload the data to saveURL (`errorReport.json`) if the entry page is invoked with `?cache-bundle=save` parameter\n    - The server must be `npm run upload` with `cacheBundleUploadService.js` to receive and save `cache-bundle.json`\n    - Parameters: `{ \"type\": \"cache-bundle.json\", \"data\": \"stringified cache-bundle.json\" }`\n  - Automate generation of `cache-bundle.json`\n    - Trigger automation by `cacheBundleGeneration.js` via `puppeteer`\n      - Invoked via `cache-bundle` gulp task\n    - Fetch a special `cache-bundle.json` at build time\n      - Generated by `cache-bundle-automation-json` gulp task\n      - Format:\n        - `\"version\": \"version_123\"`: version obtained via `get-version` gulp task\n        - `\"https://thin-hook.localhost.localdomain/automation.json\":`: `JSON.stringify()` with the object with the following properties\n          - `\"state\": \"init\"`: update state in the script to perform operations including reloading\n          - `\"serverSecret\": serverSecret`: one-time build-time-only secret for validating `cache-automation.js` script\n          - `\"script\": cacheAutomationScript`: contents of `cache-automation.js` script\n    - `cache-automation.js`: script for collecting caches by automatically navigating the target application\n      - `cache-automation.js` script is hooked with the context `https://thin-hook.localhost.localdomain/automation.json,*`\n        - ACL has to be defined for `cache-automation.js`\n      - Cache cleanup and page reload are done before `cache-automation.js` execution\n      - Cache bundle generation is performed after `cache-automation.js` execution\n        - Metadata are processed and redundant body data are converted to links to other contents with the same body data within `cache-bundle.json`\n\n- Configurations\n  - `const enableCacheBundle = true`: Use `false` and rebuild with `gulp demo` to disable `cache-bundle`\n  - For extended metadata for `cache-bundle.json`\n    - `hook.parameters.significantHeaders = { \"Header-Name\": true }`: optional\n    - `hook.parameters.cacheableContentTypes = { \"text/css\": true, \"image/png\": true, ... }`: optional\n    - `hook.parameters.validateCacheableUrl = function (url, contentType)`: optional\n  - For Service Worker\n    - `const cacheBundleURL = new URL('cache-bundle.json', hook.parameters.baseURI);`\n    - `const saveURL = new URL('errorReport.json', hook.parameters.baseURI);`\n  - `?authorization=`: `hex(sha256(serverSecret + cache-automation.js script))`\n    - Set via `encode-demo-html` gulp task\n  - For automated generation of `cache-bundle.json`\n    - `cache-automation.js` must be fully customized for the target application\n    - ACL for `cache-automation.js` with the context `https://thin-hook.localhost.localdomain/automation.json,*`\n\n### `\u003cscript src=\"hook-callback.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Features\n  - ACL for objects in HTML documents, SVG, Worker, SharedWorker\n  - Maintain `contextStack` with `Stack` class object\n    - `Stack` class object is a brancheable linked list with `push/pop` operations\n      - The branching feature of `Stack` is not utilized for now\n  - Call `hook.hookCallbackCompatibilityTest()`\n  - Attach MutationObserver to audit URLs and elements in DOM mutations\n    - Block `blob:` URLs except for downloading to local files\n    - Block unauthorized DOM mutations suspectedly from browser extensions\n      - On detection, an alert message **Blocked on Browser Extensions** is shown and the application hangs up\n  - Hook global objects\n    - Via\n      - `hooked = hook[name](Symbol.for('__hook__'), [[name, { random: name === 'Node' }]], 'method')`\n      - `Object.defineProperty(_global, name, { value: hooked, configurable: true, enumerable: false, writable: false });`\n    - Target global object names\n      - `eval`\n      - `setTimeout`\n      - `setInterval`\n      - `Node`\n      - `Element`\n      - `HTMLScriptElement`\n      - `HTMLIFrameElement`\n      - `HTMLObjectElement`\n      - `HTMLEmbedElement`\n      - `HTMLAnchorElement`\n      - `HTMLAreaElement`\n      - `Document`\n      - `importScripts`\n  - Prohibit global object access via automation like puppeteer\n    - Return `undefined` on prohibited global object access\n    - Forced redirection to `about:blank` on prohibited global object access\n- Configurations\n  - For ACL\n    - `__hook__`: hook callback function\n      - `Object.defineProperty(_global, '__hook__', { configurable: false, enumerable: false, writable: false, value: hookCallbacks.__hook__ });`\n        - `hookCallbacks.__hook__`: full features (acl + contextStack + object access graph)\n        - `hookCallbacks.__hook__acl`: acl only (acl + contextStack) - default\n        - `hookCallbacks.__hook__min`: minimal (no acl)\n    - `contextNormalizer` and `acl`\n      - Configurable at `demo-config/policy/policy.js` and included policy modules\n  - For MutationObserver\n    - `hook.parameters.mutationObserver = new MutationObserver(observerCallback);` - `MutationObserver` object set in `demo/hook-callback.js`\n    - `hook.parameters.mutationObserverConfig = { childList: true, subtree: true, attributes: true, attributeOldValue: true, characterData: true, characterDataOldValue: true, };` - Configuration options for `hook.parameters.mutationObserver.observe(options)` set in `demo/hook-callback.js`\n    - `hook.parameters.innerHTMLTracker = function (node, value, processed) {}` - Tracker callback to detect coming DOM mutations from setting `Element.innerHTML`\n    - `const detectDOMIntrusion = true;` - Use `true` to detect DOM intrusion\n    - `const messagesOnUnauthorizedMutation = { en: 'Blocked on Browser Extensions' };` - Alert messages on DOM intrusion detection, indexed for `navigator.language`\n  - For global object access\n    - `const enableDebugging = false`: Use `true` to enable debugging by disabling forced redirection to `about:blank` on prohibited global object access\n      - Configurable at `targetConfig.mode.enableDebugging` in `demo-config/config.js`\n    - `const wildcardWhitelist`: `Array` of `RegExp` for Chrome browser's `new Error().stack` format\n      - Configurable at `demo-config/policy/wildcardWhitelist.js`\n      - Example configurations for demo\n        - `new RegExp('^at (.* [(])?' + origin + '/components/'), // trust the site contents including other components`\n        - `new RegExp('^at ([^(]* [(])?' + 'https://cdnjs.cloudflare.com/ajax/libs/vis/4[.]18[.]1/vis[.]min[.]js'),`\n        - `new RegExp('^at ([^(]* [(])?' + 'https://www.gstatic.com/charts/loader[.]js'),`\n    - `const excludes = new Set() : { 'window.Math' }`: exclude `Math` object\n      - Note: `Math` object properties must be wrapped with `wrapGlobalProperty` function\n\n### `\u003cscript context-generator src=\"script-hashes.js?no-hook=true\u0026service-worker-ready=false\"\u003e\u003c/script\u003e`\n\n- Features\n  - Provide list of authorized hashes for inline scripts in HTML to accelerate preprocessing in **HTML Imports polyfill**\n    - See [Issue #275 Support HTML Imports polyfill after the native support is removed](https://github.com/t2ym/thin-hook/issues/275)\n  - Required in the entry page and HTML subdocuments\n  - The list of authorized hashes is generated in `gulp script-hashes` task and inserted into `cache-bundle.json` with the key SCRIPT_HASHES_PSEUDO_URL `https://thin-hook.localhost.localdomain/script-hashes.json`\n  - The list is empty if `service-worker-ready=false` while it is copied from `cache-bundle.json` if `service-worker-ready=true`\n    - The list is stored at `hook.parameters.scriptHashes`\n  - SRI `integrity` attribute requires **2** integrity values for both `service-worker-ready=false` and `service-worker-ready=true`\n    - They are generated in `gulp script-hashes-integrity` task and inserted into the entry page, i.e., `original-index.html` and `index.html`\n  - Unnecessary if HTML Imports feature is natively implemented in the browser or unused in the app\n    - It is recommended to append this plugin as the behaviors without the plugin are not well verified\n- Configurations\n  - Mandatory parameter `service-worker-ready=false` for the entry page, which is automatically converted to `service-worker-ready=true` after preprocessed\n  - Mandatory parameter `service-worker-ready=true` for other HTML pages including `empty-document.html`\n  - Required gulp tasks: `script-hashes`, `script-hashes-integrity`\n\n### `\u003cscript src=\"content-loader.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Features\n  - Load the target HTML without the hook infrastructure scripts after the hook infrastructure scripts are loaded in `empty-document.html` for `iframe` documents\n- Configurations\n  - The container `iframe` element is automatically configured in preprocessing HTML contents\n    - Parameter `content=base64URL(encodeURIComponent(HTML))`\n      - HTML is written into the `iframe` document via `document.write(HTML)` after preprocessing\n    - Parameter `blob=encodeURIComponent(BlobURL)`\n      - Blob is written into the `iframe` document as HTML via `document.write(fetch(Blob))` after preprocessing if the blob type is `text/html`\n      - Blob is written into the `iframe` document as a plain text via data URL if the blob type is `text/plain`\n      - Blob is blocked if the blob type is `image/svg+xml`\n      - Blob with other blob types is written into the `iframe` document as data URL\n\n### `\u003cscript src=\"wrap-globals.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Features\n  - Wrap the remaining global objects which have not been wrapped in `hook-callback.js`\n  - Put this script at the end of global API definitions after `hook-callback.js`\n  - Use `window[Symbol.for('wrapGlobalProperty')]()` to wrap global objects\n    - Defined in `hook-callback.js`\n- Configurations\n  - For global object access\n    - `const excludes = new Set();  [ 'Math' ].forEach(name =\u003e excludes.add(name));`\n      - Set of excluded objects in wrapping `window.*`\n\n#### Notes on Performance Overheads on Global Object Access\n\n- There are significant access performance overheads on global objects due to wrapped property getter/setter functions\n- To mitigate the overheads, define local alias objects for frequently used global objects\n  - For example, `const URL = window.URL, RegExp = window.RegExp, ...`\n- Internal Details on the overheads:\n  - If `contextStack` is empty, the global object is accessed outside of hooked scripts and thus `new Error().stack` has to be analyzed, which is an extremely heavy operation\n  - If `contextStack` is not empty, the global object is accessed within a hooked script, whose access can be controlled via ACL\n    - `contextStack` operations are relatively lightweight without performance degradation on deep call stack\n  - If local alias objects are defined, the corresponding global object access is performed only once per object, whose overheads are insignificant\n\n### `\u003cscript src=\"mark-parsed.js?no-hook=true\"\u003e\u003c/script\u003e`\n\n- Features\n  - Mark the parsed elements in DOM with `node[Symbol.for('parsed')] = true` at the end of HTML body to filter out valid DOM mutations from invalid ones\n  - In `iframe` documents, dispatch `srcdoc-load` event for the containing `frameElement`\n- Configurations\n  - Insert the script at the end of the entry page HTML body\n  - Use the script in `hook.parameters.bootstrap` for the `iframe` document wrapped via `hook.parameters.emptyDocumentUrl`\n\n## Server-side Components\n\nServer-side scripts and components configured for the demo but fully customizable for the target application\n\n### `demo-backend/demoServer.js`\n\nBack-end server for the demo. TBD\n\n### `demo-backend/errorReportService.js`\n\nHandler for `demo/errorReport.json` POST requests\n\n### `demo-backend/cacheBundleGeneration.js`\n\nUsed at build time to automate generation of `cache-bundle.json` via puppeteer\n\n### `demo-backend/cacheBundleUploadService.js`\n\nFormerly used at build time to automate uploading of `cache-bundle.json` via a POST request\n\n### `demo-backend/postHtml.js`\n\nExpress middleware for `demoServer.js` to handle `demo/postHtml`. This should be unnecessary and should not be used except for verification of HTML via a POST request.\n\n### `demo-backend/integrityService.js`\n\nExpress middleware for `demoServer.js` to provide integrity and double encryption of body data\n- `demo-backend/whitelist.json` - list of URL paths which are allowed to access without encryption\n- `demo-backend/blacklist.json` - list of URL paths which are not allowed to access; namely `demo/index.html`\n  - Generated in `gulp encode-demo-html` task by parsing the entry page HTML\n\n### `demo-backend/integrity-service-helpers/build/release/native.node`\n\nNode addon package compiled from the C++ source `binding.cpp` to provide the following functions\n- `rsa_oaep_decrypt(ArrayBuffer encrypted, String private_key_pem)` - Decrypt ArrayBuffer data by RSA-OAEP-SHA256 with a String private key in PEM format via `openssl`\n\n### `demo-backend/validationService.js`\n\nWhen invoked as a CLI script, it provides the validation server for `ClientIntegrity.browserHash`. TBD\n- API: TBD\n- `demo-backend/validation-console/dist/` is served at its HTTPS root\n- `demo-keys/demoCA/${process.env[\"VALIDATION_HOST\"]}.{key|crt}` is used for HTTPS server. Defaults to `localhost:8082`\n- `demo-keys/demoCA/client.{key|crt}` are used for client certificate authentication\n\nWhen imported as a package, it provides the client API for the validation server. TBD\n- `demo-keys/demoCA/client.{key|crt}` are used for client certificate authentication\n\n### `demo-backend/validation-console/dist/`\n\nValidation Console GUI served by `demo-backend/validationService.js`. TBD\n\n### `demo-keys/generate_cert.sh`\n\nScript to generate certificates in `demo-keys/demoCA/`\n\n### `demo-keys/keys.json`\n\nKey pairs and secret keys are stored for the application version.\n\n```javascript\n{\n  \"version\": \"version_668\", // application version\n  \"rsa-private-key.pem\": \"RSA PRIVATE KEY in PEM\",\n  \"rsa-public-key.pem\": \"RSA PUBLIC KEY in PEM\",\n  \"ecdsa-private-key.pem\": \"ECDSA PRIVATE KEY in PEM\",\n  \"ecdsa-public-key.pem\": \"ECDSA PUBLIC KEY in PEM\",\n  \"session-id-aes-key\": \"base64(random(32 bytes))\",\n  \"session-id-aes-iv\": \"base64(random(12 bytes))\",\n  \"scriptsHashHex\": \"hex(ClientIntegrity.scriptsHash)\",\n  \"htmlHashHex\": \"hex(ClientIntegrity.htmlHash)\"\n}\n```\n\nTBD\n\n## NPM scripts\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"wct\",\n    \"build\": \"gulp\",\n    \"demo\": \"run-p -l demoServer errorReportService validationService\",\n    \"debug\": \"run-p -l debugServer errorReportService validationService\",\n    \"https\": \"run-p -l httpsServer errorReportService validationService\",\n    \"upload\": \"run-p -l buildServer cacheBundleUploadService\",\n    \"cache-bundle\": \"run-p -r -l buildServer cacheBundleUploadService cache","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft2ym%2Fthin-hook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ft2ym%2Fthin-hook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft2ym%2Fthin-hook/lists"}