mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
feat(runtime): stabilise permissions and add event target capabilities (#9573)
This commit is contained in:
parent
90e4c5dcde
commit
097e9c44f4
17 changed files with 333 additions and 176 deletions
|
@ -19,21 +19,10 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[
|
||||||
"DiagnosticCategory",
|
"DiagnosticCategory",
|
||||||
"DiagnosticItem",
|
"DiagnosticItem",
|
||||||
"DiagnosticMessageChain",
|
"DiagnosticMessageChain",
|
||||||
"EnvPermissionDescriptor",
|
|
||||||
"HrtimePermissionDescriptor",
|
|
||||||
"HttpClient",
|
"HttpClient",
|
||||||
"LinuxSignal",
|
"LinuxSignal",
|
||||||
"Location",
|
"Location",
|
||||||
"MacOSSignal",
|
"MacOSSignal",
|
||||||
"NetPermissionDescriptor",
|
|
||||||
"PermissionDescriptor",
|
|
||||||
"PermissionName",
|
|
||||||
"PermissionState",
|
|
||||||
"PermissionStatus",
|
|
||||||
"Permissions",
|
|
||||||
"PluginPermissionDescriptor",
|
|
||||||
"ReadPermissionDescriptor",
|
|
||||||
"RunPermissionDescriptor",
|
|
||||||
"Signal",
|
"Signal",
|
||||||
"SignalStream",
|
"SignalStream",
|
||||||
"StartTlsOptions",
|
"StartTlsOptions",
|
||||||
|
@ -41,7 +30,6 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[
|
||||||
"TranspileOnlyResult",
|
"TranspileOnlyResult",
|
||||||
"UnixConnectOptions",
|
"UnixConnectOptions",
|
||||||
"UnixListenOptions",
|
"UnixListenOptions",
|
||||||
"WritePermissionDescriptor",
|
|
||||||
"applySourceMap",
|
"applySourceMap",
|
||||||
"connect",
|
"connect",
|
||||||
"consoleSize",
|
"consoleSize",
|
||||||
|
@ -64,7 +52,6 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[
|
||||||
"mainModule",
|
"mainModule",
|
||||||
"openPlugin",
|
"openPlugin",
|
||||||
"osRelease",
|
"osRelease",
|
||||||
"permissions",
|
|
||||||
"ppid",
|
"ppid",
|
||||||
"setRaw",
|
"setRaw",
|
||||||
"shutdown",
|
"shutdown",
|
||||||
|
|
134
cli/dts/lib.deno.ns.d.ts
vendored
134
cli/dts/lib.deno.ns.d.ts
vendored
|
@ -2076,6 +2076,140 @@ declare namespace Deno {
|
||||||
*/
|
*/
|
||||||
export function inspect(value: unknown, options?: InspectOptions): string;
|
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. */
|
/** Build related information. */
|
||||||
export const build: {
|
export const build: {
|
||||||
/** The LLVM target triple */
|
/** The LLVM target triple */
|
||||||
|
|
113
cli/dts/lib.deno.unstable.d.ts
vendored
113
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -1071,119 +1071,6 @@ declare namespace Deno {
|
||||||
* Requires `allow-run` permission. */
|
* Requires `allow-run` permission. */
|
||||||
export function kill(pid: number, signo: number): void;
|
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
|
/** **UNSTABLE**: New API, yet to be vetted. Additional consideration is still
|
||||||
* necessary around the permissions required.
|
* necessary around the permissions required.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2226,7 +2226,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 0,
|
"line": 0,
|
||||||
"character": 28
|
"character": 27
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -2255,9 +2255,9 @@ mod tests {
|
||||||
"contents": [
|
"contents": [
|
||||||
{
|
{
|
||||||
"language": "typescript",
|
"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": {
|
"range": {
|
||||||
"start": {
|
"start": {
|
||||||
|
@ -2266,7 +2266,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 0,
|
"line": 0,
|
||||||
"character": 28
|
"character": 27
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const status1 = await Deno.permissions.request({ name: "read", path: "foo" });
|
const status1 =
|
||||||
const status2 = await Deno.permissions.query({ name: "read", path: "bar" });
|
(await Deno.permissions.request({ name: "read", path: "foo" })).state;
|
||||||
const status3 = await Deno.permissions.request({ name: "read", path: "bar" });
|
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(status1);
|
||||||
console.log(status2);
|
console.log(status2);
|
||||||
console.log(status3);
|
console.log(status3);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[WILDCARD]PermissionStatus { state: "granted" }
|
[WILDCARD]granted
|
||||||
PermissionStatus { state: "prompt" }
|
prompt
|
||||||
PermissionStatus { state: "denied" }
|
denied
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const status1 = await Deno.permissions.request({ name: "read" });
|
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);
|
console.log(status1);
|
||||||
|
const status2 = await Deno.permissions.query({ name: "read", path: "foo" });
|
||||||
console.log(status2);
|
console.log(status2);
|
||||||
|
const status3 = await Deno.permissions.query({ name: "read", path: "bar" });
|
||||||
console.log(status3);
|
console.log(status3);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[WILDCARD]PermissionStatus { state: "granted" }
|
[WILDCARD]PermissionStatus { state: "granted", onchange: null }
|
||||||
PermissionStatus { state: "granted" }
|
PermissionStatus { state: "granted", onchange: null }
|
||||||
PermissionStatus { state: "granted" }
|
PermissionStatus { state: "granted", onchange: null }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const status1 = await Deno.permissions.revoke({ name: "read", path: "foo" });
|
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);
|
console.log(status1);
|
||||||
|
const status2 = await Deno.permissions.query({ name: "read", path: "bar" });
|
||||||
console.log(status2);
|
console.log(status2);
|
||||||
|
const status3 = await Deno.permissions.revoke({ name: "read", path: "bar" });
|
||||||
console.log(status3);
|
console.log(status3);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[WILDCARD]PermissionStatus { state: "prompt" }
|
[WILDCARD]PermissionStatus { state: "prompt", onchange: null }
|
||||||
PermissionStatus { state: "granted" }
|
PermissionStatus { state: "granted", onchange: null }
|
||||||
PermissionStatus { state: "prompt" }
|
PermissionStatus { state: "prompt", onchange: null }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const status1 = await Deno.permissions.revoke({ name: "read" });
|
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);
|
console.log(status1);
|
||||||
|
const status2 = await Deno.permissions.query({ name: "read", path: "foo" });
|
||||||
console.log(status2);
|
console.log(status2);
|
||||||
|
const status3 = await Deno.permissions.query({ name: "read", path: "bar" });
|
||||||
console.log(status3);
|
console.log(status3);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[WILDCARD]PermissionStatus { state: "prompt" }
|
[WILDCARD]PermissionStatus { state: "prompt", onchange: null }
|
||||||
PermissionStatus { state: "prompt" }
|
PermissionStatus { state: "prompt", onchange: null }
|
||||||
PermissionStatus { state: "prompt" }
|
PermissionStatus { state: "prompt", onchange: null }
|
||||||
|
|
|
@ -2677,7 +2677,7 @@ console.log("finish");
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn _061_permissions_request() {
|
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 output = "061_permissions_request.ts.out";
|
||||||
let input = b"g\nd\n";
|
let input = b"g\nd\n";
|
||||||
|
|
||||||
|
@ -2687,7 +2687,7 @@ console.log("finish");
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn _062_permissions_request_global() {
|
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 output = "062_permissions_request_global.ts.out";
|
||||||
let input = b"g\n";
|
let input = b"g\n";
|
||||||
|
|
||||||
|
@ -2695,13 +2695,12 @@ console.log("finish");
|
||||||
}
|
}
|
||||||
|
|
||||||
itest!(_063_permissions_revoke {
|
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",
|
output: "063_permissions_revoke.ts.out",
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(_064_permissions_revoke_global {
|
itest!(_064_permissions_revoke_global {
|
||||||
args:
|
args: "run --allow-read=foo,bar 064_permissions_revoke_global.ts",
|
||||||
"run --unstable --allow-read=foo,bar 064_permissions_revoke_global.ts",
|
|
||||||
output: "064_permissions_revoke_global.ts.out",
|
output: "064_permissions_revoke_global.ts.out",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"uri": "file:///a/file.ts",
|
"uri": "file:///a/file.ts",
|
||||||
"languageId": "typescript",
|
"languageId": "typescript",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"text": "console.log(Deno.permissions);\n"
|
"text": "console.log(Deno.openPlugin);\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
import {
|
import {
|
||||||
|
assert,
|
||||||
assertEquals,
|
assertEquals,
|
||||||
assertThrows,
|
assertThrows,
|
||||||
assertThrowsAsync,
|
assertThrowsAsync,
|
||||||
|
@ -10,7 +11,7 @@ unitTest(async function permissionInvalidName(): Promise<void> {
|
||||||
await assertThrowsAsync(async () => {
|
await assertThrowsAsync(async () => {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
await Deno.permissions.query({ name: "foo" as any });
|
await Deno.permissions.query({ name: "foo" as any });
|
||||||
}, Error);
|
}, TypeError);
|
||||||
});
|
});
|
||||||
|
|
||||||
unitTest(async function permissionNetInvalidHost(): Promise<void> {
|
unitTest(async function permissionNetInvalidHost(): Promise<void> {
|
||||||
|
@ -19,8 +20,33 @@ unitTest(async function permissionNetInvalidHost(): Promise<void> {
|
||||||
}, URIError);
|
}, 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() {
|
unitTest(function permissionsIllegalConstructor() {
|
||||||
assertThrows(() => new Deno.Permissions(), TypeError, "Illegal constructor.");
|
assertThrows(() => new Deno.Permissions(), TypeError, "Illegal constructor.");
|
||||||
|
assertEquals(Deno.Permissions.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
unitTest(function permissionStatusIllegalConstructor() {
|
unitTest(function permissionStatusIllegalConstructor() {
|
||||||
|
|
|
@ -2,57 +2,178 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
const core = window.Deno.core;
|
const {
|
||||||
const { illegalConstructorKey } = window.__bootstrap.webUtil;
|
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) {
|
function opQuery(desc) {
|
||||||
return core.jsonOpSync("op_query_permission", desc).state;
|
return core.jsonOpSync("op_query_permission", desc).state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Deno.PermissionDescriptor} desc
|
||||||
|
* @returns {Deno.PermissionState}
|
||||||
|
*/
|
||||||
function opRevoke(desc) {
|
function opRevoke(desc) {
|
||||||
return core.jsonOpSync("op_revoke_permission", desc).state;
|
return core.jsonOpSync("op_revoke_permission", desc).state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Deno.PermissionDescriptor} desc
|
||||||
|
* @returns {Deno.PermissionState}
|
||||||
|
*/
|
||||||
function opRequest(desc) {
|
function opRequest(desc) {
|
||||||
return core.jsonOpSync("op_request_permission", desc).state;
|
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) {
|
constructor(state = null, key = null) {
|
||||||
if (key != illegalConstructorKey) {
|
if (key != illegalConstructorKey) {
|
||||||
throw new TypeError("Illegal constructor.");
|
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<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;
|
||||||
|
}
|
||||||
|
/** @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 {
|
class Permissions {
|
||||||
constructor(key) {
|
constructor(key = null) {
|
||||||
if (key != illegalConstructorKey) {
|
if (key != illegalConstructorKey) {
|
||||||
throw new TypeError("Illegal constructor.");
|
throw new TypeError("Illegal constructor.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query(desc) {
|
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);
|
const state = opQuery(desc);
|
||||||
return Promise.resolve(
|
return Promise.resolve(cache(desc, state));
|
||||||
new PermissionStatus(state, illegalConstructorKey),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
revoke(desc) {
|
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);
|
const state = opRevoke(desc);
|
||||||
return Promise.resolve(
|
return Promise.resolve(cache(desc, state));
|
||||||
new PermissionStatus(state, illegalConstructorKey),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request(desc) {
|
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);
|
const state = opRequest(desc);
|
||||||
return Promise.resolve(
|
return Promise.resolve(cache(desc, state));
|
||||||
new PermissionStatus(state, illegalConstructorKey),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,9 @@
|
||||||
fsync: __bootstrap.fs.fsync,
|
fsync: __bootstrap.fs.fsync,
|
||||||
fdatasyncSync: __bootstrap.fs.fdatasyncSync,
|
fdatasyncSync: __bootstrap.fs.fdatasyncSync,
|
||||||
fdatasync: __bootstrap.fs.fdatasync,
|
fdatasync: __bootstrap.fs.fdatasync,
|
||||||
|
permissions: __bootstrap.permissions.permissions,
|
||||||
|
Permissions: __bootstrap.permissions.Permissions,
|
||||||
|
PermissionStatus: __bootstrap.permissions.PermissionStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
__bootstrap.denoNsUnstable = {
|
__bootstrap.denoNsUnstable = {
|
||||||
|
@ -96,9 +99,6 @@
|
||||||
Signal: __bootstrap.signals.Signal,
|
Signal: __bootstrap.signals.Signal,
|
||||||
SignalStream: __bootstrap.signals.SignalStream,
|
SignalStream: __bootstrap.signals.SignalStream,
|
||||||
emit: __bootstrap.compilerApi.emit,
|
emit: __bootstrap.compilerApi.emit,
|
||||||
permissions: __bootstrap.permissions.permissions,
|
|
||||||
Permissions: __bootstrap.permissions.Permissions,
|
|
||||||
PermissionStatus: __bootstrap.permissions.PermissionStatus,
|
|
||||||
openPlugin: __bootstrap.plugins.openPlugin,
|
openPlugin: __bootstrap.plugins.openPlugin,
|
||||||
kill: __bootstrap.process.kill,
|
kill: __bootstrap.process.kill,
|
||||||
setRaw: __bootstrap.tty.setRaw,
|
setRaw: __bootstrap.tty.setRaw,
|
||||||
|
|
Loading…
Reference in a new issue