web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

web.ts (4731B)


      1 import type { IClientGeolocationPosition } from "@radroots/geo";
      2 import { err_msg, type ResolveErrorMsg } from "@radroots/utils";
      3 import { cl_geolocation_error, type ClientGeolocationErrorMessage } from "./error.js";
      4 import type { IClientGeolocation } from "./types.js";
      5 
      6 type GeoPolicyAllows = boolean | "unknown";
      7 type GeoPermissionState = PermissionState | "unknown";
      8 
      9 interface PermissionsPolicyLike {
     10     allowsFeature(feature: "geolocation"): boolean;
     11 }
     12 
     13 interface DocumentWithPermissionsPolicy extends Document {
     14     permissionsPolicy: PermissionsPolicyLike;
     15 }
     16 
     17 interface NavigatorWithPermissions extends Navigator {
     18     permissions: Permissions;
     19 }
     20 
     21 interface GeoDebug {
     22     policy_allows: GeoPolicyAllows;
     23     permission_state: GeoPermissionState;
     24     error_code?: number;
     25     error_message?: string;
     26     user_agent: string;
     27 }
     28 
     29 const geo_debug_enabled = true;
     30 
     31 function has_permissions_policy(doc: Document): doc is DocumentWithPermissionsPolicy {
     32     return "permissionsPolicy" in doc;
     33 }
     34 
     35 function has_permissions_api(nav: Navigator): nav is NavigatorWithPermissions {
     36     return "permissions" in nav;
     37 }
     38 
     39 function read_policy_allows_geolocation(doc: Document): GeoPolicyAllows {
     40     if (!has_permissions_policy(doc)) return "unknown";
     41     try {
     42         return doc.permissionsPolicy.allowsFeature("geolocation");
     43     } catch {
     44         return "unknown";
     45     }
     46 }
     47 
     48 async function read_permission_state_geolocation(nav: Navigator): Promise<GeoPermissionState> {
     49     if (!has_permissions_api(nav)) return "unknown";
     50     try {
     51         const status = await nav.permissions.query({ name: "geolocation" });
     52         return status.state;
     53     } catch {
     54         return "unknown";
     55     }
     56 }
     57 
     58 function create_debug(
     59     policy_allows: GeoPolicyAllows,
     60     permission_state: GeoPermissionState
     61 ): GeoDebug {
     62     return {
     63         policy_allows,
     64         permission_state,
     65         user_agent: navigator.userAgent
     66     };
     67 }
     68 
     69 function log_geo_debug(event: string, debug: GeoDebug): void {
     70     if (!geo_debug_enabled) return;
     71     console.debug(event, debug);
     72 }
     73 
     74 function get_current_position(): Promise<GeolocationPosition> {
     75     return new Promise<GeolocationPosition>((resolve, reject) => {
     76         navigator.geolocation.getCurrentPosition(resolve, reject, {
     77             enableHighAccuracy: true,
     78             timeout: 10000,
     79             maximumAge: 30000
     80         });
     81     });
     82 }
     83 
     84 function map_error_key(
     85     debug: GeoDebug,
     86     error: GeolocationPositionError
     87 ) {
     88     if (error.code === 1) {
     89         if (debug.policy_allows === false) {
     90             return cl_geolocation_error.blocked_by_permissions_policy;
     91         }
     92         return cl_geolocation_error.permission_denied;
     93     }
     94 
     95     if (error.code === 2) {
     96         return cl_geolocation_error.position_unavailable;
     97     }
     98 
     99     if (error.code === 3) {
    100         return cl_geolocation_error.timeout;
    101     }
    102 
    103     return cl_geolocation_error.unknown_error;
    104 }
    105 
    106 export interface IWebGeolocation extends IClientGeolocation {}
    107 
    108 export class WebGeolocation implements IWebGeolocation {
    109     public async current(): Promise<ResolveErrorMsg<IClientGeolocationPosition, ClientGeolocationErrorMessage>> {
    110         if (typeof navigator === "undefined" || typeof document === "undefined") return err_msg(cl_geolocation_error.location_unavailable);
    111         if (!navigator.geolocation) return err_msg(cl_geolocation_error.location_unavailable);
    112 
    113         const policy_allows = read_policy_allows_geolocation(document);
    114         const permission_state = await read_permission_state_geolocation(navigator);
    115 
    116         const base_debug = create_debug(policy_allows, permission_state);
    117         const has_geo_error = typeof GeolocationPositionError !== "undefined";
    118 
    119         if (policy_allows === false) {
    120             log_geo_debug("[geolocation] blocked_by_policy", base_debug);
    121             return err_msg(cl_geolocation_error.blocked_by_permissions_policy);
    122         }
    123 
    124         try {
    125             const position = await get_current_position();
    126 
    127             return {
    128                 lat: position.coords.latitude,
    129                 lng: position.coords.longitude,
    130                 accuracy: position.coords.accuracy
    131             };
    132         } catch (e) {
    133             if (has_geo_error && e instanceof GeolocationPositionError) {
    134                 const debug: GeoDebug = {
    135                     ...base_debug,
    136                     error_code: e.code,
    137                     error_message: e.message
    138                 };
    139 
    140                 const key = map_error_key(debug, e);
    141                 log_geo_debug("[geolocation] error", debug);
    142 
    143                 return err_msg(key);
    144             }
    145 
    146             log_geo_debug("[geolocation] unknown_exception", base_debug);
    147             return err_msg(cl_geolocation_error.unknown_error);
    148         }
    149     }
    150 }