/** * @description 表示可用于 Web 权限查询与订阅的权限名称。 * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Permissions} */ export type WebPermissionName = | PermissionName | "accelerometer" | "accessibility-events" | "ambient-light-sensor" | "background-sync" | "camera" | "capture-surface-control" | "clipboard-read" | "clipboard-write" | "geolocation" | "gyroscope" | "local-fonts" | "magnetometer" | "microphone" | "midi" | "notifications" | "payment-handler" | "persistent-storage" | "push" | "screen-wake-lock" | "top-level-storage-access" | "window-management" /** * @description 表示查询单个 Web 权限时的输入选项。 */ export interface QueryPermissionOptions { name: WebPermissionName } /** * @description 表示单个 Web 权限查询后的标准化结果。 */ export interface QueryPermissionResult { name: WebPermissionName state: PermissionState | "unsupported" | "exception" detail?: string | undefined } /** * @description 查询指定 Web 权限的当前状态。 * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query} */ export const queryPermission = async ( options: QueryPermissionOptions, ): Promise => { const { name } = options try { const permissionStatus = await navigator.permissions.query({ name: name as PermissionName, }) return { name, state: permissionStatus.state, } } catch (exception) { if (exception instanceof TypeError) { return { name, state: "unsupported", detail: exception.message, } } else if (exception instanceof DOMException) { return { name, state: "exception", detail: exception.message, } } else { return { name, state: "exception", detail: String(exception), } } } } /** * @description 确保指定权限已被授予;如果权限状态不为 "granted",则抛出错误。 */ export const ensurePermissionGranted = async (options: QueryPermissionOptions): Promise => { try { const queryResult = await queryPermission(options) if (queryResult.state !== "granted") { throw new Error( `Permission "${options.name}" is not granted. Current state: ${queryResult.state}`, ) } } catch (exception) { throw new Error(`Failed to ensure permission "${options.name}"`, { cause: exception }) } } /** * @description 表示订阅单个 Web 权限变化时的输入选项。 */ export interface SubscribePermissionOptions { name: WebPermissionName subscribe: (queryPermissionResult: QueryPermissionResult) => void } /** * @description 订阅指定权限状态的变化。当权限状态发生变化时,回调函数会被调用并传入最新的权限状态。 */ export const subscribePermission = (options: SubscribePermissionOptions): (() => void) => { const { name, subscribe } = options let unsubscribed = false let internalUnsubscribe: (() => void) | undefined = undefined const unsubscribe = (): void => { unsubscribed = true internalUnsubscribe?.() } try { void navigator.permissions .query({ name: name as PermissionName, }) .then((permissionStatus) => { if (unsubscribed === true) { return } const internalSubscribe = (): void => { subscribe({ name, state: permissionStatus.state, }) } permissionStatus.addEventListener("change", internalSubscribe) internalUnsubscribe = (): void => { permissionStatus.removeEventListener("change", internalSubscribe) } }) .catch((exception: unknown) => { if (unsubscribed === true) { return } if (exception instanceof TypeError) { subscribe({ name, state: "unsupported", detail: exception.message, }) } else if (exception instanceof DOMException) { subscribe({ name, state: "exception", detail: exception.message, }) } else { subscribe({ name, state: "exception", detail: String(exception), }) } }) } catch (exception: unknown) { subscribe({ name, state: "unsupported", detail: String(exception), }) } return unsubscribe }