1+ import { isPageReload } from '@telegram-apps/navigation' ;
2+ import { getStorageValue , Maybe , setStorageValue } from '@telegram-apps/toolkit' ;
3+ import { AbortablePromise } from 'better-promises' ;
4+ import type { EventPayload } from '@telegram-apps/bridge' ;
5+ import type { Computed } from '@telegram-apps/signals' ;
6+
7+ import { defineMountFn } from '@/scopes/defineMountFn.js' ;
8+ import { request } from '@/globals.js' ;
9+ import { createWrapComplete } from '@/scopes/wrappers/createWrapComplete.js' ;
10+ import { createWrapSupported } from '@/scopes/wrappers/createWrapSupported.js' ;
11+ import { NotAvailableError } from '@/errors.js' ;
12+ import { defineNonConcurrentFn } from '@/scopes/defineNonConcurrentFn.js' ;
13+ import { signalCancel } from '@/scopes/signalCancel.js' ;
14+ import type { AsyncOptions } from '@/types.js' ;
15+ import { createComputed , createSignal } from '@/signals-registry.js' ;
16+
17+ const COMPONENT_NAME = 'locationManager' ;
18+ const CHECK_LOCATION_METHOD = 'web_app_check_location' ;
19+
20+ export interface State {
21+ /**
22+ * If true, indicates that location data tracking is available on the current device.
23+ */
24+ available : boolean ;
25+ /**
26+ * Indicates whether the app has previously requested permission to track location data.
27+ */
28+ accessRequested : boolean ;
29+ /**
30+ * Indicates whether the user has granted the app permission to track location data.
31+ *
32+ * If false and `accessRequested` is true may indicate that:
33+ *
34+ * - The user has simply canceled the permission popup.
35+ * - The user has denied the app permission to track location data.
36+ */
37+ accessGranted : boolean ;
38+ }
39+
40+ type StorageValue = State ;
41+
42+ const state = createSignal < State > ( {
43+ available : false ,
44+ accessGranted : false ,
45+ accessRequested : false ,
46+ } ) ;
47+
48+ function fromState < K extends keyof State > ( key : K ) : Computed < State [ K ] > {
49+ return createComputed ( ( ) => state ( ) [ key ] ) ;
50+ }
51+
52+ /**
53+ * Signal indicating whether the location data tracking is currently available.
54+ */
55+ export const isAvailable = fromState ( 'available' ) ;
56+
57+ /**
58+ * Signal indicating whether the user has granted the app permission to track location data.
59+ */
60+ export const isAccessGranted = fromState ( 'accessGranted' ) ;
61+
62+ /**
63+ * Signal indicating whether the app has previously requested permission to track location data.
64+ */
65+ export const isAccessRequested = fromState ( 'accessRequested' ) ;
66+
67+ /**
68+ * Converts `location_checked` to some common shape.
69+ * @param event - event payload.
70+ * @see location_checked
71+ */
72+ function eventToState ( event : EventPayload < 'location_checked' > ) : State {
73+ console . log ( event ) ;
74+ let available = false ;
75+ let accessRequested : Maybe < boolean > ;
76+ let accessGranted : Maybe < boolean > ;
77+ if ( event . available ) {
78+ available = true ;
79+ accessRequested = event . access_requested ;
80+ accessGranted = event . access_granted ;
81+ }
82+ return {
83+ available,
84+ accessGranted : accessGranted || false ,
85+ accessRequested : accessRequested || false ,
86+ } ;
87+ }
88+
89+ const [
90+ mountFn ,
91+ tMountPromise ,
92+ tMountError ,
93+ tIsMounted ,
94+ ] = defineMountFn (
95+ COMPONENT_NAME ,
96+ ( options ?: AsyncOptions ) => {
97+ const s = isPageReload ( ) && getStorageValue < StorageValue > ( COMPONENT_NAME ) ;
98+ return s
99+ ? AbortablePromise . resolve ( s )
100+ : request ( 'web_app_check_location' , 'location_checked' , options ) . then ( eventToState ) ;
101+ } ,
102+ s => {
103+ state . set ( s ) ;
104+ setStorageValue < State > ( COMPONENT_NAME , s ) ;
105+ } ,
106+ ) ;
107+
108+ const wrapSupported = createWrapSupported ( COMPONENT_NAME , CHECK_LOCATION_METHOD ) ;
109+ const wrapComplete = createWrapComplete ( COMPONENT_NAME , tIsMounted [ 0 ] , CHECK_LOCATION_METHOD ) ;
110+
111+ /**
112+ * Mounts the location manager component.
113+ * @since Mini Apps v8.0
114+ * @throws {FunctionNotAvailableError } The environment is unknown
115+ * @throws {FunctionNotAvailableError } The SDK is not initialized
116+ * @throws {FunctionNotAvailableError } The function is not supported
117+ * @example
118+ * if (mount.isAvailable()) {
119+ * await mount();
120+ * }
121+ */
122+ export const mount = wrapSupported ( 'mount' , mountFn ) ;
123+ export const [ , mountPromise , isMounting ] = tMountPromise ;
124+ export const [ , mountError ] = tMountError ;
125+ export const [ _isMounted , isMounted ] = tIsMounted ;
126+
127+ const [
128+ reqLocationFn ,
129+ tReqLocationPromise ,
130+ tReqLocationError ,
131+ ] = defineNonConcurrentFn (
132+ ( options ?: AsyncOptions ) => {
133+ return request ( 'web_app_request_location' , 'location_requested' , options ) . then ( data => {
134+ if ( ! data . available ) {
135+ state . set ( { ...state ( ) , available : false } ) ;
136+ throw new NotAvailableError ( 'Location data tracking is not available' ) ;
137+ }
138+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
139+ const { available, ...rest } = data ;
140+ return rest ;
141+ } ) ;
142+ } ,
143+ 'Location request is currently in progress' ,
144+ ) ;
145+
146+
147+ /**
148+ * Requests location data.
149+ * @since Mini Apps v8.0
150+ * @returns Promise with location data.
151+ * @throws {FunctionNotAvailableError } The environment is unknown
152+ * @throws {FunctionNotAvailableError } The SDK is not initialized
153+ * @throws {FunctionNotAvailableError } The function is not supported
154+ * @throws {FunctionNotAvailableError } The parent component is not mounted
155+ * @throws {ConcurrentCallError } Location request is currently in progress
156+ * @throws {NotAvailableError } Location data tracking is not available
157+ * @example
158+ * if (requestLocation.isAvailable()) {
159+ * const location = await requestLocation();
160+ * }
161+ */
162+ export const requestLocation = wrapComplete ( 'getLocation' , reqLocationFn ) ;
163+ export const [ , requestLocationPromise , isRequestingLocation ] = tReqLocationPromise ;
164+ export const [ , requestLocationError ] = tReqLocationError ;
165+
166+
167+ /**
168+ * Unmounts the component.
169+ */
170+ export function unmount ( ) : void {
171+ signalCancel ( requestLocationPromise ) ;
172+ _isMounted . set ( false ) ;
173+ }
0 commit comments