diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-02-25 14:33:09 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-25 14:33:09 +1100 |
commit | 097e9c44f4d4c7daae7d8113c391bd24d29e7119 (patch) | |
tree | dd6b8bd34c9e762f42eadc59ebcfcbca7e4faf43 /runtime/js | |
parent | 90e4c5dcde99c5f0a1aaa18c0ad786613174085b (diff) |
feat(runtime): stabilise permissions and add event target capabilities (#9573)
Diffstat (limited to 'runtime/js')
-rw-r--r-- | runtime/js/40_permissions.js | 151 | ||||
-rw-r--r-- | runtime/js/90_deno_ns.js | 6 |
2 files changed, 139 insertions, 18 deletions
diff --git a/runtime/js/40_permissions.js b/runtime/js/40_permissions.js index 2b4acb595..d7ed5a433 100644 --- a/runtime/js/40_permissions.js +++ b/runtime/js/40_permissions.js @@ -2,57 +2,178 @@ "use strict"; ((window) => { - const core = window.Deno.core; - const { illegalConstructorKey } = window.__bootstrap.webUtil; + const { + Event, + EventTarget, + Deno: { core }, + __bootstrap: { webUtil: { illegalConstructorKey } }, + } = window; + /** + * @typedef StatusCacheValue + * @property {PermissionState} state + * @property {PermissionStatus} status + */ + + /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "run" | "plugin" | "hrtime">} */ + const permissionNames = [ + "read", + "write", + "net", + "env", + "run", + "plugin", + "hrtime", + ]; + + /** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ function opQuery(desc) { return core.jsonOpSync("op_query_permission", desc).state; } + /** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ function opRevoke(desc) { return core.jsonOpSync("op_revoke_permission", desc).state; } + /** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ function opRequest(desc) { return core.jsonOpSync("op_request_permission", desc).state; } - class PermissionStatus { + class PermissionStatus extends EventTarget { + /** @type {{ state: Deno.PermissionState }} */ + #state; + + /** @type {((this: PermissionStatus, event: Event) => any) | null} */ + onchange = null; + + /** @returns {Deno.PermissionState} */ + get state() { + return this.#state.state; + } + + /** + * @param {{ state: Deno.PermissionState }} state + * @param {unknown} key + */ constructor(state = null, key = null) { if (key != illegalConstructorKey) { throw new TypeError("Illegal constructor."); } - this.state = state; + super(); + this.#state = state; + } + + /** + * @param {Event} event + * @returns {boolean} + */ + dispatchEvent(event) { + let dispatched = super.dispatchEvent(event); + if (dispatched && this.onchange) { + this.onchange.call(this, event); + dispatched = !event.defaultPrevented; + } + return dispatched; + } + + [Symbol.for("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ state: this.state, onchange: this.onchange }) + }`; + } + } + + /** @type {Map<string, StatusCacheValue>} */ + const statusCache = new Map(); + + /** + * + * @param {Deno.PermissionDescriptor} desc + * @param {Deno.PermissionState} state + * @returns {PermissionStatus} + */ + function cache(desc, state) { + let { name: key } = desc; + if ((desc.name === "read" || desc.name === "write") && "path" in desc) { + key += `-${desc.path}`; + } else if (desc.name === "net" && desc.host) { + key += `-${desc.host}`; + } + if (statusCache.has(key)) { + const status = statusCache.get(key); + if (status.state !== state) { + status.state = state; + status.status.dispatchEvent(new Event("change", { cancelable: false })); + } + return status.status; } - // TODO(kt3k): implement onchange handler + /** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */ + const status = { state }; + status.status = new PermissionStatus(status, illegalConstructorKey); + statusCache.set(key, status); + return status.status; + } + + /** + * @param {unknown} desc + * @returns {desc is Deno.PermissionDescriptor} + */ + function isValidDescriptor(desc) { + return desc && desc !== null && permissionNames.includes(desc.name); } class Permissions { - constructor(key) { + constructor(key = null) { if (key != illegalConstructorKey) { throw new TypeError("Illegal constructor."); } } query(desc) { + if (!isValidDescriptor(desc)) { + return Promise.reject( + new TypeError( + `The provided value "${desc.name}" is not a valid permission name.`, + ), + ); + } const state = opQuery(desc); - return Promise.resolve( - new PermissionStatus(state, illegalConstructorKey), - ); + return Promise.resolve(cache(desc, state)); } revoke(desc) { + if (!isValidDescriptor(desc)) { + return Promise.reject( + new TypeError( + `The provided value "${desc.name}" is not a valid permission name.`, + ), + ); + } const state = opRevoke(desc); - return Promise.resolve( - new PermissionStatus(state, illegalConstructorKey), - ); + return Promise.resolve(cache(desc, state)); } request(desc) { + if (!isValidDescriptor(desc)) { + return Promise.reject( + new TypeError( + `The provided value "${desc.name}" is not a valid permission name.`, + ), + ); + } const state = opRequest(desc); - return Promise.resolve( - new PermissionStatus(state, illegalConstructorKey), - ); + return Promise.resolve(cache(desc, state)); } } diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index 84c084062..84c6b7ade 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -88,6 +88,9 @@ fsync: __bootstrap.fs.fsync, fdatasyncSync: __bootstrap.fs.fdatasyncSync, fdatasync: __bootstrap.fs.fdatasync, + permissions: __bootstrap.permissions.permissions, + Permissions: __bootstrap.permissions.Permissions, + PermissionStatus: __bootstrap.permissions.PermissionStatus, }; __bootstrap.denoNsUnstable = { @@ -96,9 +99,6 @@ Signal: __bootstrap.signals.Signal, SignalStream: __bootstrap.signals.SignalStream, emit: __bootstrap.compilerApi.emit, - permissions: __bootstrap.permissions.permissions, - Permissions: __bootstrap.permissions.Permissions, - PermissionStatus: __bootstrap.permissions.PermissionStatus, openPlugin: __bootstrap.plugins.openPlugin, kill: __bootstrap.process.kill, setRaw: __bootstrap.tty.setRaw, |