summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-02-25 14:33:09 +1100
committerGitHub <noreply@github.com>2021-02-25 14:33:09 +1100
commit097e9c44f4d4c7daae7d8113c391bd24d29e7119 (patch)
treedd6b8bd34c9e762f42eadc59ebcfcbca7e4faf43
parent90e4c5dcde99c5f0a1aaa18c0ad786613174085b (diff)
feat(runtime): stabilise permissions and add event target capabilities (#9573)
-rw-r--r--cli/diagnostics.rs13
-rw-r--r--cli/dts/lib.deno.ns.d.ts134
-rw-r--r--cli/dts/lib.deno.unstable.d.ts113
-rw-r--r--cli/lsp/language_server.rs8
-rw-r--r--cli/tests/061_permissions_request.ts9
-rw-r--r--cli/tests/061_permissions_request.ts.out6
-rw-r--r--cli/tests/062_permissions_request_global.ts4
-rw-r--r--cli/tests/062_permissions_request_global.ts.out6
-rw-r--r--cli/tests/063_permissions_revoke.ts4
-rw-r--r--cli/tests/063_permissions_revoke.ts.out6
-rw-r--r--cli/tests/064_permissions_revoke_global.ts4
-rw-r--r--cli/tests/064_permissions_revoke_global.ts.out6
-rw-r--r--cli/tests/integration_tests.rs9
-rw-r--r--cli/tests/lsp/did_open_notification_unstable.json2
-rw-r--r--cli/tests/unit/permissions_test.ts28
-rw-r--r--runtime/js/40_permissions.js151
-rw-r--r--runtime/js/90_deno_ns.js6
17 files changed, 333 insertions, 176 deletions
diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs
index 4f1ae0c96..1da2f277e 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 1627843e4..15088934a 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 `"<hostname>[:<port>]"`. 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<K extends keyof PermissionStatusEventMap>(
+ type: K,
+ listener: (
+ this: PermissionStatus,
+ ev: PermissionStatusEventMap[K],
+ ) => any,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ removeEventListener<K extends keyof PermissionStatusEventMap>(
+ 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<PermissionStatus>;
+
+ /** 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<PermissionStatus>;
+
+ /** 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<PermissionStatus>;
+ }
+
+ /** 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 a9353e2ff..8623e73d4 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 `"<hostname>[:<port>]"`. 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<PermissionStatus>;
-
- /** 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<PermissionStatus>;
-
- /** 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<PermissionStatus>;
- }
-
- /** **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 e6880f0df..755151a24 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 8fdc2c590..c31e7ac42 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 de058a9a3..362425876 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 4ed98ff64..e431bc31b 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 69b5ee50d..57b5aa7d8 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 e61883693..a81eee7cb 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 803893e9c..bbd64c557 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 efe74b828..a9b1fcd40 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 a2ea05cb7..f7e389a76 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 f9f458016..497028cd1 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 583eafdef..bb7a1f679 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 2dcb67abf..8a6d7e9aa 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<void> {
await assertThrowsAsync(async () => {
// deno-lint-ignore no-explicit-any
await Deno.permissions.query({ name: "foo" as any });
- }, Error);
+ }, TypeError);
});
unitTest(async function permissionNetInvalidHost(): Promise<void> {
@@ -19,8 +20,33 @@ unitTest(async function permissionNetInvalidHost(): Promise<void> {
}, 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 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,