From 097e9c44f4d4c7daae7d8113c391bd24d29e7119 Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Thu, 25 Feb 2021 14:33:09 +1100 Subject: [PATCH] feat(runtime): stabilise permissions and add event target capabilities (#9573) --- cli/diagnostics.rs | 13 -- cli/dts/lib.deno.ns.d.ts | 134 ++++++++++++++++ cli/dts/lib.deno.unstable.d.ts | 113 ------------- cli/lsp/language_server.rs | 8 +- cli/tests/061_permissions_request.ts | 9 +- cli/tests/061_permissions_request.ts.out | 6 +- cli/tests/062_permissions_request_global.ts | 4 +- .../062_permissions_request_global.ts.out | 6 +- cli/tests/063_permissions_revoke.ts | 4 +- cli/tests/063_permissions_revoke.ts.out | 6 +- cli/tests/064_permissions_revoke_global.ts | 4 +- .../064_permissions_revoke_global.ts.out | 6 +- cli/tests/integration_tests.rs | 9 +- .../lsp/did_open_notification_unstable.json | 2 +- cli/tests/unit/permissions_test.ts | 28 +++- runtime/js/40_permissions.js | 151 ++++++++++++++++-- runtime/js/90_deno_ns.js | 6 +- 17 files changed, 333 insertions(+), 176 deletions(-) diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs index 4f1ae0c967..1da2f277e2 100644 --- a/cli/diagnostics.rs +++ b/cli/diagnostics.rs @@ -19,21 +19,10 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[ "DiagnosticCategory", "DiagnosticItem", "DiagnosticMessageChain", - "EnvPermissionDescriptor", - "HrtimePermissionDescriptor", "HttpClient", "LinuxSignal", "Location", "MacOSSignal", - "NetPermissionDescriptor", - "PermissionDescriptor", - "PermissionName", - "PermissionState", - "PermissionStatus", - "Permissions", - "PluginPermissionDescriptor", - "ReadPermissionDescriptor", - "RunPermissionDescriptor", "Signal", "SignalStream", "StartTlsOptions", @@ -41,7 +30,6 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[ "TranspileOnlyResult", "UnixConnectOptions", "UnixListenOptions", - "WritePermissionDescriptor", "applySourceMap", "connect", "consoleSize", @@ -64,7 +52,6 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[ "mainModule", "openPlugin", "osRelease", - "permissions", "ppid", "setRaw", "shutdown", diff --git a/cli/dts/lib.deno.ns.d.ts b/cli/dts/lib.deno.ns.d.ts index 1627843e4c..15088934a3 100644 --- a/cli/dts/lib.deno.ns.d.ts +++ b/cli/dts/lib.deno.ns.d.ts @@ -2076,6 +2076,140 @@ declare namespace Deno { */ export function inspect(value: unknown, options?: InspectOptions): string; + /** The name of a "powerful feature" which needs permission. */ + export type PermissionName = + | "run" + | "read" + | "write" + | "net" + | "env" + | "plugin" + | "hrtime"; + + /** The current status of the permission. */ + export type PermissionState = "granted" | "denied" | "prompt"; + + export interface RunPermissionDescriptor { + name: "run"; + } + + export interface ReadPermissionDescriptor { + name: "read"; + path?: string; + } + + export interface WritePermissionDescriptor { + name: "write"; + path?: string; + } + + export interface NetPermissionDescriptor { + name: "net"; + /** Optional host string of the form `"[:]"`. Examples: + * + * "github.com" + * "deno.land:8080" + */ + host?: string; + } + + export interface EnvPermissionDescriptor { + name: "env"; + } + + export interface PluginPermissionDescriptor { + name: "plugin"; + } + + export interface HrtimePermissionDescriptor { + name: "hrtime"; + } + + /** Permission descriptors which define a permission and can be queried, + * requested, or revoked. */ + export type PermissionDescriptor = + | RunPermissionDescriptor + | ReadPermissionDescriptor + | WritePermissionDescriptor + | NetPermissionDescriptor + | EnvPermissionDescriptor + | PluginPermissionDescriptor + | HrtimePermissionDescriptor; + + export interface PermissionStatusEventMap { + "change": Event; + } + + export class PermissionStatus extends EventTarget { + // deno-lint-ignore no-explicit-any + onchange: ((this: PermissionStatus, ev: Event) => any) | null; + readonly state: PermissionState; + addEventListener( + type: K, + listener: ( + this: PermissionStatus, + ev: PermissionStatusEventMap[K], + ) => any, + options?: boolean | AddEventListenerOptions, + ): void; + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions, + ): void; + removeEventListener( + type: K, + listener: ( + this: PermissionStatus, + ev: PermissionStatusEventMap[K], + ) => any, + options?: boolean | EventListenerOptions, + ): void; + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions, + ): void; + } + + export class Permissions { + /** Resolves to the current status of a permission. + * + * ```ts + * const status = await Deno.permissions.query({ name: "read", path: "/etc" }); + * if (status.state === "granted") { + * data = await Deno.readFile("/etc/passwd"); + * } + * ``` + */ + query(desc: PermissionDescriptor): Promise; + + /** Revokes a permission, and resolves to the state of the permission. + * + * ```ts + * const status = await Deno.permissions.revoke({ name: "run" }); + * assert(status.state !== "granted") + * ``` + */ + revoke(desc: PermissionDescriptor): Promise; + + /** Requests the permission, and resolves to the state of the permission. + * + * ```ts + * const status = await Deno.permissions.request({ name: "env" }); + * if (status.state === "granted") { + * console.log("'env' permission is granted."); + * } else { + * console.log("'env' permission is denied."); + * } + * ``` + */ + request(desc: PermissionDescriptor): Promise; + } + + /** Deno's permission management API. */ + export const permissions: Permissions; + /** Build related information. */ export const build: { /** The LLVM target triple */ diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index a9353e2ff1..8623e73d44 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -1071,119 +1071,6 @@ declare namespace Deno { * Requires `allow-run` permission. */ export function kill(pid: number, signo: number): void; - /** The name of a "powerful feature" which needs permission. - * - * See: https://w3c.github.io/permissions/#permission-registry - * - * Note that the definition of `PermissionName` in the above spec is swapped - * out for a set of Deno permissions which are not web-compatible. */ - export type PermissionName = - | "run" - | "read" - | "write" - | "net" - | "env" - | "plugin" - | "hrtime"; - - /** The current status of the permission. - * - * See: https://w3c.github.io/permissions/#status-of-a-permission */ - export type PermissionState = "granted" | "denied" | "prompt"; - - export interface RunPermissionDescriptor { - name: "run"; - } - - export interface ReadPermissionDescriptor { - name: "read"; - path?: string; - } - - export interface WritePermissionDescriptor { - name: "write"; - path?: string; - } - - export interface NetPermissionDescriptor { - name: "net"; - /** Optional host string of the form `"[:]"`. Examples: - * - * "github.com" - * "deno.land:8080" - */ - host?: string; - } - - export interface EnvPermissionDescriptor { - name: "env"; - } - - export interface PluginPermissionDescriptor { - name: "plugin"; - } - - export interface HrtimePermissionDescriptor { - name: "hrtime"; - } - - /** Permission descriptors which define a permission and can be queried, - * requested, or revoked. - * - * See: https://w3c.github.io/permissions/#permission-descriptor */ - export type PermissionDescriptor = - | RunPermissionDescriptor - | ReadPermissionDescriptor - | WritePermissionDescriptor - | NetPermissionDescriptor - | EnvPermissionDescriptor - | PluginPermissionDescriptor - | HrtimePermissionDescriptor; - - export class Permissions { - /** Resolves to the current status of a permission. - * - * ```ts - * const status = await Deno.permissions.query({ name: "read", path: "/etc" }); - * if (status.state === "granted") { - * data = await Deno.readFile("/etc/passwd"); - * } - * ``` - */ - query(desc: PermissionDescriptor): Promise; - - /** Revokes a permission, and resolves to the state of the permission. - * - * const status = await Deno.permissions.revoke({ name: "run" }); - * assert(status.state !== "granted") - */ - revoke(desc: PermissionDescriptor): Promise; - - /** Requests the permission, and resolves to the state of the permission. - * - * ```ts - * const status = await Deno.permissions.request({ name: "env" }); - * if (status.state === "granted") { - * console.log("'env' permission is granted."); - * } else { - * console.log("'env' permission is denied."); - * } - * ``` - */ - request(desc: PermissionDescriptor): Promise; - } - - /** **UNSTABLE**: Under consideration to move to `navigator.permissions` to - * match web API. It could look like `navigator.permissions.query({ name: Deno.symbols.read })`. - */ - export const permissions: Permissions; - - /** see: https://w3c.github.io/permissions/#permissionstatus */ - export class PermissionStatus { - state: PermissionState; - constructor(); - } - /** **UNSTABLE**: New API, yet to be vetted. Additional consideration is still * necessary around the permissions required. * diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index e6880f0df0..755151a240 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -2226,7 +2226,7 @@ mod tests { }, "end": { "line": 0, - "character": 28 + "character": 27 } } }), @@ -2255,9 +2255,9 @@ mod tests { "contents": [ { "language": "typescript", - "value": "const Deno.permissions: Deno.Permissions" + "value": "function Deno.openPlugin(filename: string): number" }, - "**UNSTABLE**: Under consideration to move to `navigator.permissions` to\nmatch web API. It could look like `navigator.permissions.query({ name: Deno.symbols.read })`." + "**UNSTABLE**: new API, yet to be vetted.\n\nOpen and initialize a plugin.\n\n```ts\nconst rid = Deno.openPlugin(\"./path/to/some/plugin.so\");\nconst opId = Deno.core.ops()[\"some_op\"];\nconst response = Deno.core.dispatch(opId, new Uint8Array([1,2,3,4]));\nconsole.log(`Response from plugin ${response}`);\n```\n\nRequires `allow-plugin` permission.\n\nThe plugin system is not stable and will change in the future, hence the\nlack of docs. For now take a look at the example\nhttps://github.com/denoland/deno/tree/master/test_plugin" ], "range": { "start": { @@ -2266,7 +2266,7 @@ mod tests { }, "end": { "line": 0, - "character": 28 + "character": 27 } } }), diff --git a/cli/tests/061_permissions_request.ts b/cli/tests/061_permissions_request.ts index 8fdc2c5904..c31e7ac42c 100644 --- a/cli/tests/061_permissions_request.ts +++ b/cli/tests/061_permissions_request.ts @@ -1,6 +1,9 @@ -const status1 = await Deno.permissions.request({ name: "read", path: "foo" }); -const status2 = await Deno.permissions.query({ name: "read", path: "bar" }); -const status3 = await Deno.permissions.request({ name: "read", path: "bar" }); +const status1 = + (await Deno.permissions.request({ name: "read", path: "foo" })).state; +const status2 = + (await Deno.permissions.query({ name: "read", path: "bar" })).state; +const status3 = + (await Deno.permissions.request({ name: "read", path: "bar" })).state; console.log(status1); console.log(status2); console.log(status3); diff --git a/cli/tests/061_permissions_request.ts.out b/cli/tests/061_permissions_request.ts.out index de058a9a36..362425876a 100644 --- a/cli/tests/061_permissions_request.ts.out +++ b/cli/tests/061_permissions_request.ts.out @@ -1,3 +1,3 @@ -[WILDCARD]PermissionStatus { state: "granted" } -PermissionStatus { state: "prompt" } -PermissionStatus { state: "denied" } +[WILDCARD]granted +prompt +denied diff --git a/cli/tests/062_permissions_request_global.ts b/cli/tests/062_permissions_request_global.ts index 4ed98ff641..e431bc31be 100644 --- a/cli/tests/062_permissions_request_global.ts +++ b/cli/tests/062_permissions_request_global.ts @@ -1,6 +1,6 @@ const status1 = await Deno.permissions.request({ name: "read" }); -const status2 = await Deno.permissions.query({ name: "read", path: "foo" }); -const status3 = await Deno.permissions.query({ name: "read", path: "bar" }); console.log(status1); +const status2 = await Deno.permissions.query({ name: "read", path: "foo" }); console.log(status2); +const status3 = await Deno.permissions.query({ name: "read", path: "bar" }); console.log(status3); diff --git a/cli/tests/062_permissions_request_global.ts.out b/cli/tests/062_permissions_request_global.ts.out index 69b5ee50d3..57b5aa7d84 100644 --- a/cli/tests/062_permissions_request_global.ts.out +++ b/cli/tests/062_permissions_request_global.ts.out @@ -1,3 +1,3 @@ -[WILDCARD]PermissionStatus { state: "granted" } -PermissionStatus { state: "granted" } -PermissionStatus { state: "granted" } +[WILDCARD]PermissionStatus { state: "granted", onchange: null } +PermissionStatus { state: "granted", onchange: null } +PermissionStatus { state: "granted", onchange: null } diff --git a/cli/tests/063_permissions_revoke.ts b/cli/tests/063_permissions_revoke.ts index e618836934..a81eee7cb2 100644 --- a/cli/tests/063_permissions_revoke.ts +++ b/cli/tests/063_permissions_revoke.ts @@ -1,6 +1,6 @@ const status1 = await Deno.permissions.revoke({ name: "read", path: "foo" }); -const status2 = await Deno.permissions.query({ name: "read", path: "bar" }); -const status3 = await Deno.permissions.revoke({ name: "read", path: "bar" }); console.log(status1); +const status2 = await Deno.permissions.query({ name: "read", path: "bar" }); console.log(status2); +const status3 = await Deno.permissions.revoke({ name: "read", path: "bar" }); console.log(status3); diff --git a/cli/tests/063_permissions_revoke.ts.out b/cli/tests/063_permissions_revoke.ts.out index 803893e9c0..bbd64c557b 100644 --- a/cli/tests/063_permissions_revoke.ts.out +++ b/cli/tests/063_permissions_revoke.ts.out @@ -1,3 +1,3 @@ -[WILDCARD]PermissionStatus { state: "prompt" } -PermissionStatus { state: "granted" } -PermissionStatus { state: "prompt" } +[WILDCARD]PermissionStatus { state: "prompt", onchange: null } +PermissionStatus { state: "granted", onchange: null } +PermissionStatus { state: "prompt", onchange: null } diff --git a/cli/tests/064_permissions_revoke_global.ts b/cli/tests/064_permissions_revoke_global.ts index efe74b8280..a9b1fcd406 100644 --- a/cli/tests/064_permissions_revoke_global.ts +++ b/cli/tests/064_permissions_revoke_global.ts @@ -1,6 +1,6 @@ const status1 = await Deno.permissions.revoke({ name: "read" }); -const status2 = await Deno.permissions.query({ name: "read", path: "foo" }); -const status3 = await Deno.permissions.query({ name: "read", path: "bar" }); console.log(status1); +const status2 = await Deno.permissions.query({ name: "read", path: "foo" }); console.log(status2); +const status3 = await Deno.permissions.query({ name: "read", path: "bar" }); console.log(status3); diff --git a/cli/tests/064_permissions_revoke_global.ts.out b/cli/tests/064_permissions_revoke_global.ts.out index a2ea05cb7f..f7e389a76b 100644 --- a/cli/tests/064_permissions_revoke_global.ts.out +++ b/cli/tests/064_permissions_revoke_global.ts.out @@ -1,3 +1,3 @@ -[WILDCARD]PermissionStatus { state: "prompt" } -PermissionStatus { state: "prompt" } -PermissionStatus { state: "prompt" } +[WILDCARD]PermissionStatus { state: "prompt", onchange: null } +PermissionStatus { state: "prompt", onchange: null } +PermissionStatus { state: "prompt", onchange: null } diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index f9f458016b..497028cd1c 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -2677,7 +2677,7 @@ console.log("finish"); #[cfg(unix)] #[test] fn _061_permissions_request() { - let args = "run --unstable 061_permissions_request.ts"; + let args = "run 061_permissions_request.ts"; let output = "061_permissions_request.ts.out"; let input = b"g\nd\n"; @@ -2687,7 +2687,7 @@ console.log("finish"); #[cfg(unix)] #[test] fn _062_permissions_request_global() { - let args = "run --unstable 062_permissions_request_global.ts"; + let args = "run 062_permissions_request_global.ts"; let output = "062_permissions_request_global.ts.out"; let input = b"g\n"; @@ -2695,13 +2695,12 @@ console.log("finish"); } itest!(_063_permissions_revoke { - args: "run --unstable --allow-read=foo,bar 063_permissions_revoke.ts", + args: "run --allow-read=foo,bar 063_permissions_revoke.ts", output: "063_permissions_revoke.ts.out", }); itest!(_064_permissions_revoke_global { - args: - "run --unstable --allow-read=foo,bar 064_permissions_revoke_global.ts", + args: "run --allow-read=foo,bar 064_permissions_revoke_global.ts", output: "064_permissions_revoke_global.ts.out", }); diff --git a/cli/tests/lsp/did_open_notification_unstable.json b/cli/tests/lsp/did_open_notification_unstable.json index 583eafdefb..bb7a1f6792 100644 --- a/cli/tests/lsp/did_open_notification_unstable.json +++ b/cli/tests/lsp/did_open_notification_unstable.json @@ -6,7 +6,7 @@ "uri": "file:///a/file.ts", "languageId": "typescript", "version": 1, - "text": "console.log(Deno.permissions);\n" + "text": "console.log(Deno.openPlugin);\n" } } } diff --git a/cli/tests/unit/permissions_test.ts b/cli/tests/unit/permissions_test.ts index 2dcb67abf4..8a6d7e9aa1 100644 --- a/cli/tests/unit/permissions_test.ts +++ b/cli/tests/unit/permissions_test.ts @@ -1,5 +1,6 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. import { + assert, assertEquals, assertThrows, assertThrowsAsync, @@ -10,7 +11,7 @@ unitTest(async function permissionInvalidName(): Promise { await assertThrowsAsync(async () => { // deno-lint-ignore no-explicit-any await Deno.permissions.query({ name: "foo" as any }); - }, Error); + }, TypeError); }); unitTest(async function permissionNetInvalidHost(): Promise { @@ -19,8 +20,33 @@ unitTest(async function permissionNetInvalidHost(): Promise { }, URIError); }); +unitTest(async function permissionQueryReturnsEventTarget() { + const status = await Deno.permissions.query({ name: "hrtime" }); + assert(["granted", "denied", "prompt"].includes(status.state)); + let called = false; + status.addEventListener("change", () => { + called = true; + }); + status.dispatchEvent(new Event("change")); + assert(called); + assert(status === (await Deno.permissions.query({ name: "hrtime" }))); +}); + +unitTest(async function permissionQueryForReadReturnsSameStatus() { + const status1 = await Deno.permissions.query({ + name: "read", + path: ".", + }); + const status2 = await Deno.permissions.query({ + name: "read", + path: ".", + }); + assert(status1 === status2); +}); + unitTest(function permissionsIllegalConstructor() { assertThrows(() => new Deno.Permissions(), TypeError, "Illegal constructor."); + assertEquals(Deno.Permissions.length, 0); }); unitTest(function permissionStatusIllegalConstructor() { diff --git a/runtime/js/40_permissions.js b/runtime/js/40_permissions.js index 2b4acb595c..d7ed5a4336 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; } - // TODO(kt3k): implement onchange handler + + /** + * @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} */ + 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; + } + /** @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 84c0840626..84c6b7ade0 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,