@de./sdk-rn 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/dist/allend/Access.d.ts +12 -0
  4. package/dist/allend/Access.d.ts.map +1 -0
  5. package/dist/allend/Access.js +43 -0
  6. package/dist/allend/Access.js.map +1 -0
  7. package/dist/allend/DClient/Client.d.ts +11 -0
  8. package/dist/allend/DClient/Client.d.ts.map +1 -0
  9. package/dist/allend/DClient/Client.js +50 -0
  10. package/dist/allend/DClient/Client.js.map +1 -0
  11. package/dist/allend/DClient/Event.d.ts +19 -0
  12. package/dist/allend/DClient/Event.d.ts.map +1 -0
  13. package/dist/allend/DClient/Event.js +74 -0
  14. package/dist/allend/DClient/Event.js.map +1 -0
  15. package/dist/allend/DClient/Order.d.ts +50 -0
  16. package/dist/allend/DClient/Order.d.ts.map +1 -0
  17. package/dist/allend/DClient/Order.js +321 -0
  18. package/dist/allend/DClient/Order.js.map +1 -0
  19. package/dist/allend/MSI/Controls.d.ts +232 -0
  20. package/dist/allend/MSI/Controls.d.ts.map +1 -0
  21. package/dist/allend/MSI/Controls.js +668 -0
  22. package/dist/allend/MSI/Controls.js.map +1 -0
  23. package/dist/allend/MSI/Handles.d.ts +89 -0
  24. package/dist/allend/MSI/Handles.d.ts.map +1 -0
  25. package/dist/allend/MSI/Handles.js +328 -0
  26. package/dist/allend/MSI/Handles.js.map +1 -0
  27. package/dist/allend/MSI/Plugins.d.ts +23 -0
  28. package/dist/allend/MSI/Plugins.d.ts.map +1 -0
  29. package/dist/allend/MSI/Plugins.js +29 -0
  30. package/dist/allend/MSI/Plugins.js.map +1 -0
  31. package/dist/allend/MSI/index.d.ts +38 -0
  32. package/dist/allend/MSI/index.d.ts.map +1 -0
  33. package/dist/allend/MSI/index.js +176 -0
  34. package/dist/allend/MSI/index.js.map +1 -0
  35. package/dist/allend/index.d.ts +16 -0
  36. package/dist/allend/index.d.ts.map +1 -0
  37. package/dist/allend/index.js +12 -0
  38. package/dist/allend/index.js.map +1 -0
  39. package/dist/backend/Auth.d.ts +14 -0
  40. package/dist/backend/Auth.d.ts.map +1 -0
  41. package/dist/backend/Auth.js +84 -0
  42. package/dist/backend/Auth.js.map +1 -0
  43. package/dist/index.d.ts +13 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +12 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/utils/index.d.ts +6 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/index.js +3 -0
  50. package/dist/utils/index.js.map +1 -0
  51. package/dist/utils/stream.d.ts +21 -0
  52. package/dist/utils/stream.d.ts.map +1 -0
  53. package/dist/utils/stream.js +59 -0
  54. package/dist/utils/stream.js.map +1 -0
  55. package/package.json +81 -0
  56. package/src/allend/Access.ts +58 -0
  57. package/src/allend/DClient/Client.ts +76 -0
  58. package/src/allend/DClient/Event.ts +76 -0
  59. package/src/allend/DClient/Order.ts +452 -0
  60. package/src/allend/MSI/Controls.ts +703 -0
  61. package/src/allend/MSI/Handles.ts +387 -0
  62. package/src/allend/MSI/Plugins.ts +52 -0
  63. package/src/allend/MSI/index.tsx +254 -0
  64. package/src/allend/index.ts +14 -0
  65. package/src/backend/Auth.ts +112 -0
  66. package/src/index.ts +18 -0
  67. package/src/types/access.d.ts +7 -0
  68. package/src/types/auth.d.ts +26 -0
  69. package/src/types/index.d.ts +275 -0
  70. package/src/utils/index.ts +4 -0
  71. package/src/utils/stream.ts +68 -0
@@ -0,0 +1,387 @@
1
+ import type {
2
+ Coordinates,
3
+ GPSLocation,
4
+ Entity,
5
+ MapOptions,
6
+ Caption,
7
+ Journey,
8
+ ActivePosition,
9
+ PickedLocation,
10
+ RouteOptions
11
+ } from '../../types'
12
+
13
+ import WIO from 'webview.io'
14
+ import { EventEmitter } from 'events'
15
+ import Controls from './Controls'
16
+ import Stream from '../../utils/stream'
17
+
18
+ export interface ControlEntity {
19
+ add: ( entity: Entity, callback?: () => void ) => void
20
+ remove: ( id: string, callback?: () => void ) => void
21
+ move: ( update: ActivePosition, callback?: () => void ) => void
22
+ }
23
+ export type LRSControlsListener = ( controls: ControlEntity ) => void
24
+ export type LRSErrorListener = ( error?: Error | boolean ) => void
25
+
26
+ export default class Handles extends EventEmitter {
27
+ private chn: WIO
28
+ private controls: Controls
29
+ private options: MapOptions
30
+
31
+ constructor( chn: WIO, controls: Controls, options: MapOptions ){
32
+ super()
33
+
34
+ this.chn = chn
35
+ this.options = options
36
+ this.controls = controls
37
+ }
38
+
39
+ /**
40
+ * Listen when user manually picked a location
41
+ * on the map.
42
+ *
43
+ * @param fn - Event listener function
44
+ * @return - void
45
+ */
46
+ onPickLocation( fn: ( location: PickedLocation ) => void ){
47
+ this.chn.on('pick:location', fn )
48
+ }
49
+
50
+ /**
51
+ * Create new stream through which user's current location
52
+ * details will be pushed to the API level.
53
+ *
54
+ * @param usertype - (Default: `client`) Type of user at the current location
55
+ * @return - Readable stream
56
+ */
57
+ myLocation( usertype?: 'client' | 'agent' ){
58
+ if( !this.chn ) return
59
+ this.chn?.emit('pin:current:location', usertype || 'client' )
60
+
61
+ const stream = new Stream()
62
+
63
+ this.chn
64
+ .on('current:location', ( location: GPSLocation ) => stream.sync( location ) )
65
+ .on('live:location:start', ( location: GPSLocation ) => stream.sync( location ) )
66
+ .on('live:location:update', ( location: GPSLocation ) => stream.sync( location ) )
67
+ .on('live:location:end', ( location: GPSLocation ) => stream.sync( location ) )
68
+ .on('current:location:error', ( message: string ) => stream.error( new Error( message ) ) )
69
+
70
+ // Listen to stream closed
71
+ stream
72
+ .onerror( error => console.error('[Stream Error] ', error ) )
73
+ .onclose( () => {
74
+ if( !this.chn ) return
75
+
76
+ this.chn
77
+ .off('current:location')
78
+ .off('current:location:live')
79
+ .off('current:location:error')
80
+ } )
81
+
82
+ return stream
83
+ }
84
+
85
+ /**
86
+ * Create new stream through which the current location details
87
+ * of user's peer (Eg. `agent`) will be pushed to the API level.
88
+ *
89
+ * @param position - Peer's current GPS location
90
+ * @param caption - (Optional) Caption information of the peer
91
+ * @return - Readable stream
92
+ */
93
+ peerLocation( position: GPSLocation , caption?: Caption ){
94
+ if( !this.chn ) return
95
+ this.chn?.emit('pin:peer:location', { id: 'peer', position, caption } )
96
+
97
+ const stream = new Stream
98
+
99
+ // Listen to incoming new position data
100
+ stream
101
+ .on('data', ({ position, caption }: any ) => {
102
+ if( !position )
103
+ return stream.error( new Error('Invalid Data') )
104
+
105
+ this.chn?.emit('pin:peer:location', { id: 'peer', position, caption } )
106
+ })
107
+ .onerror( error => console.error('[Stream Error] ', error ) )
108
+ .onclose( () => this.chn?.emit('unpin:peer:location', 'peer') )
109
+
110
+ return stream
111
+ }
112
+
113
+ /**
114
+ * Open a live stream through which the current location details
115
+ * and profile information of all nearby entities around
116
+ * this user (Eg. `bike`, `car`, ...) will be pushed to the
117
+ * API level.
118
+ *
119
+ * @param list - List of detected nearby entities around this user.
120
+ * @return - Live Readable Stream (LRS)
121
+ */
122
+ nearby( list: Entity[] ){
123
+ if( !this.chn ) return
124
+
125
+ const self = this
126
+ let _CLOSED = false
127
+
128
+ this.chn?.emit('show:nearby', list )
129
+
130
+ const
131
+ stream = new Stream,
132
+ controls: ControlEntity = {
133
+ /**
134
+ * Add new entity to the nearby list
135
+ *
136
+ * @param entity - GPS location and profile of the entity
137
+ */
138
+ add( entity ): Promise<void> {
139
+ return new Promise( ( resolve, reject ) => {
140
+ if( _CLOSED ) return
141
+
142
+ // Maintain initial list of nearby: Prevent duplicated entity
143
+ for( let x = 0; x < list.length; x++ )
144
+ if( list[x].id === entity.id ){
145
+ list.splice( x, 1 )
146
+ self.chn?.emit('remove:nearby:entity', entity.id )
147
+ break
148
+ }
149
+
150
+ // Track response timeout
151
+ let TIMEOUT: any
152
+
153
+ // Add new entity
154
+ list.push( entity )
155
+ self.chn?.emit('add:nearby:entity', entity, () => {
156
+ clearTimeout( TIMEOUT )
157
+ resolve()
158
+
159
+ self.emit('nearby--stream', 'add', entity )
160
+ } )
161
+
162
+ setTimeout( () => reject('Add entity timeout'), 8000 )
163
+ })
164
+ },
165
+
166
+ /**
167
+ * Remove an entity (vehicle, premises) from the nearby list
168
+ *
169
+ * @param id - ID of targeted entity
170
+ */
171
+ remove( id ): Promise<void> {
172
+ return new Promise( ( resolve, reject ) => {
173
+ if( _CLOSED ) return
174
+
175
+ // Track response timeout
176
+ let TIMEOUT: any
177
+
178
+ list = list.filter( each => { return each.id !== id } )
179
+ self.chn?.emit('remove:nearby:entity', id, () => {
180
+ clearTimeout( TIMEOUT )
181
+ resolve()
182
+
183
+ self.emit('nearby--stream', 'remove', id )
184
+ } )
185
+
186
+ setTimeout( () => reject('Remove entity timeout'), 8000 )
187
+ } )
188
+ },
189
+
190
+ /**
191
+ * Change mobile entities position on the map
192
+ *
193
+ * @param location - New GPS location of the entity
194
+ */
195
+ move( location ): Promise<void> {
196
+ return new Promise( ( resolve, reject ) => {
197
+ if( _CLOSED ) return
198
+
199
+ // Track response timeout
200
+ let TIMEOUT: any
201
+
202
+ self.chn?.emit('move:nearby:entity', location, () => {
203
+ clearTimeout( TIMEOUT )
204
+ resolve()
205
+
206
+ self.emit('nearby--stream', 'move', location )
207
+ } )
208
+
209
+ setTimeout( () => reject('Remove entity timeout'), 8000 )
210
+ } )
211
+ }
212
+ }
213
+
214
+ const
215
+ /**
216
+ *
217
+ */
218
+ live = ( fn: LRSControlsListener ) => {
219
+ fn( controls )
220
+ return stream
221
+ },
222
+ close = ( fn?: LRSErrorListener ) => {
223
+ if( _CLOSED ) return
224
+
225
+ this.chn?.emit('remove:nearby', ( error: string | boolean ) => {
226
+ if( typeof error == 'string' )
227
+ return typeof fn == 'function' && fn( new Error( error ) )
228
+
229
+ _CLOSED = true
230
+ stream.isActive() && stream.close()
231
+
232
+ typeof fn == 'function' && fn()
233
+ })
234
+ }
235
+
236
+ this.on('nearby--stream', ( action, dataset ) => stream.sync({ action, dataset, list }) )
237
+ // Listen to stream closed
238
+ stream
239
+ .onerror( error => console.error('[Stream Error] ', error ) )
240
+ .onclose( ( fn?: () => void ) => {
241
+ this.off('nearby--stream', fn || (() => {}) )
242
+ close()
243
+ })
244
+
245
+ return { live, pipe: stream.pipe, close }
246
+ }
247
+
248
+ /**
249
+ * Set service pickup point.
250
+ *
251
+ * @param location - Location coordinates
252
+ * @param caption - (Optional) Caption information of the pickup point
253
+ */
254
+ async pickupPoint( location: Coordinates, caption?: Caption ): Promise<void> {
255
+ // Default pickup caption
256
+ const _caption: Caption = {
257
+ label: 'Pickup point',
258
+ ...(caption || {})
259
+ }
260
+
261
+ await this.controls?.setRouteOrigin('main', { coords: location, caption: _caption } )
262
+ }
263
+
264
+ /**
265
+ * Set service dropoff point.
266
+ *
267
+ * @param location - Location coordinates
268
+ * @param caption - (Optional) Caption information of the pickup point
269
+ */
270
+ async dropoffPoint( location: Coordinates, caption?: Caption ): Promise<void> {
271
+ // Default destination caption
272
+ const _caption: Caption = {
273
+ label: 'Destination point',
274
+ ...(caption || {})
275
+ }
276
+
277
+ await this.controls?.setRouteDestination('main', { coords: location, caption: _caption } )
278
+ }
279
+
280
+ /**
281
+ * Create new stream through which navigation details of
282
+ * this user's peer will be display on the user's map also
283
+ * pushed to the API level.
284
+ *
285
+ * @return - Readable stream
286
+ */
287
+ peerDirection( options?: RouteOptions ){
288
+ if( !this.chn ) return
289
+ const stream = new Stream
290
+
291
+ stream
292
+ .on('data', ({ status, direction, position }: any ) => {
293
+ if( !direction || !position )
294
+ return stream.error( new Error('Invalid Data') )
295
+
296
+ this.controls?.casting('peer-direction', direction, position, options )
297
+
298
+ switch( status ){
299
+ case 'STALE':
300
+ case 'STARTED':
301
+ case 'LONG_STOP':
302
+ case 'LOW_TRAFFIC':
303
+ case 'HIGH_TRAFFIC':
304
+ case 'MODERATE_TRAFFIC':
305
+ case 'SPEED_WARNING':
306
+ case 'NEARBY':
307
+ case 'ARRIVED': this.emit(`pe:${status.toLowerCase()}`); break
308
+ case 'UNAVAILABLE': {
309
+ this.emit(`pe:closed`)
310
+ stream.isActive() && stream.close()
311
+ } break
312
+ }
313
+ })
314
+ .onerror( error => console.error('[Stream Error] ', error ) )
315
+
316
+ return stream
317
+ }
318
+
319
+ /**
320
+ * Initiate user (Eg. agent) live navigation on the map
321
+ * and create a new stream through which the navigation details
322
+ * will be pushed to the API level.
323
+ *
324
+ * @param journey - Route origin, waypoints, destination
325
+ * @return - Readable stream
326
+ */
327
+ navigation( journey: Journey ): Promise<Stream> {
328
+ return new Promise( ( resolve, reject ) => {
329
+ if( !this.chn ) return
330
+
331
+ const initialize = () => {
332
+ const stream = new Stream
333
+
334
+ // Sync with navigation route update
335
+ this.chn?.on('navigation:direction', ({ status, direction, position }) => {
336
+ stream.sync({ status, direction, position })
337
+
338
+ switch( status ){
339
+ case 'STALE':
340
+ case 'STARTED':
341
+ case 'LONG_STOP':
342
+ case 'LOW_TRAFFIC':
343
+ case 'HIGH_TRAFFIC':
344
+ case 'MODERATE_TRAFFIC':
345
+ case 'SPEED_WARNING':
346
+ case 'NEARBY':
347
+ case 'ARRIVED': this.emit(`pe:${status.toLowerCase()}`); break
348
+ case 'UNAVAILABLE': {
349
+ this.emit(`pe:closed`)
350
+ stream.isActive() && stream.close()
351
+ } break
352
+ }
353
+ })
354
+
355
+ stream
356
+ // Listen location/position update
357
+ .on('data', ({ position }: any ) => {
358
+ if( !position )
359
+ return stream.error( new Error('Invalid Data') )
360
+
361
+ this.controls?.navigate( position )
362
+ })
363
+ .onerror( error => console.error('[Stream Error] ', error ) )
364
+ .onclose( () => {
365
+ this.chn?.off('navigation:direction')
366
+ this.controls?.unmountNavigation()
367
+ })
368
+
369
+ resolve( stream )
370
+ }
371
+
372
+ // Set route
373
+ this.controls?.setRoute( journey )
374
+ .then( async () => {
375
+ // Initialize navigation point to current location
376
+ const position = journey.origin || await this.controls?.getCurrentLocation()
377
+ if( !position ) return reject('Unable to get current location')
378
+
379
+ initialize()
380
+
381
+ await this.controls?.mountNavigation( journey.routeId )
382
+ await this.controls?.setInitialNavigationPosition( position as GPSLocation )
383
+ } )
384
+ .catch( reject )
385
+ } )
386
+ }
387
+ }
@@ -0,0 +1,52 @@
1
+ import type { MapOptions } from '../../types'
2
+
3
+ import WIO from 'webview.io'
4
+ import Handles from './Handles'
5
+ import Controls from './Controls'
6
+ import Utils from '../../utils'
7
+
8
+ export type PluginHook = {
9
+ handles: Handles,
10
+ controls: Controls,
11
+ map: MapOptions,
12
+ utils: typeof Utils
13
+ }
14
+ export type Plugin<PluginAPI, PluginOptions = {}> = ( hooks: PluginHook, options?: PluginOptions ) => PluginAPI
15
+
16
+ export default class Plugins {
17
+ private chn: WIO
18
+ private handles: Handles
19
+ private controls: Controls
20
+ private options: MapOptions
21
+ private ACTIVE_PLUGINS: Record<string, Plugin<any>> = {}
22
+
23
+ constructor( chn: WIO, handles: Handles, controls: Controls, options: MapOptions ){
24
+ this.chn = chn
25
+ this.options = options
26
+ this.handles = handles
27
+ this.controls = controls
28
+ }
29
+
30
+ mount( list: Record<string, Plugin<any>> ){
31
+ Object.entries( list ).forEach( ([name, plugin]) => {
32
+ // TODO: Allow api level permission settings
33
+
34
+ // TODO: Put validation checks in place
35
+
36
+ this.ACTIVE_PLUGINS[ name ] = plugin
37
+ } )
38
+ }
39
+
40
+ use<API, Options>( name: string, options?: Record<string, Options> ){
41
+ if( !(name in this.ACTIVE_PLUGINS) )
42
+ throw new Error(`Undefined <${name}> plugin`)
43
+
44
+ const plugin: Plugin<API> = this.ACTIVE_PLUGINS[ name ]
45
+ return plugin({
46
+ handles: this.handles,
47
+ controls: this.controls,
48
+ map: this.options,
49
+ utils: Utils
50
+ }, options )
51
+ }
52
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * de.sdk-rn
3
+ * React Native SDK for MSI (Map Service Interface)
4
+ */
5
+
6
+ import React, { useRef, useEffect, useState, forwardRef, useImperativeHandle } from 'react'
7
+ import { View, StyleSheet, AppState, AppStateStatus } from 'react-native'
8
+ import { WebView } from 'react-native-webview'
9
+ import WIO from 'webview.io'
10
+ import Controls from './Controls'
11
+ import Handles from './Handles'
12
+ import Plugins, { type Plugin } from './Plugins'
13
+ import type { MapOptions } from '../../types'
14
+
15
+ export interface MSIInterface {
16
+ controls: Controls
17
+ handles: Handles
18
+ plugins: Plugins
19
+ }
20
+
21
+ const REQUIRED_FEATURES = ['geolocation']
22
+ const REGISTERED_PLUGINS: Record<string, Plugin<any>> = {}
23
+
24
+ export interface MSIProps extends MapOptions {
25
+ onReady?: () => void
26
+ onError?: (error: Error) => void
27
+ onLoaded?: (msi: MSIInterface) => void
28
+ }
29
+
30
+ export interface MSIRef extends MSIInterface {
31
+ isReady: () => boolean
32
+ }
33
+
34
+ // MSI Component
35
+ export default forwardRef<MSIRef, MSIProps>(( props, ref ) => {
36
+ const webViewRef = useRef<WebView>(null)
37
+ const wioRef = useRef<WIO | null>(null)
38
+ const controlsRef = useRef<Controls | null>(null)
39
+ const handlesRef = useRef<Handles | null>(null)
40
+ const pluginsRef = useRef<Plugins | null>(null)
41
+ const [isConnected, setIsConnected] = useState(false)
42
+ const [hasError, setHasError] = useState(false)
43
+
44
+ // Expose API via ref
45
+ useImperativeHandle( ref, () => ({
46
+ controls: controlsRef.current!,
47
+ handles: handlesRef.current!,
48
+ plugins: pluginsRef.current!,
49
+ isReady: () => isConnected
50
+ }))
51
+
52
+ useEffect(() => {
53
+ // Initialize WIO bridge
54
+ wioRef.current = new WIO({
55
+ type: 'WEBVIEW',
56
+ debug: props.env === 'dev'
57
+ })
58
+
59
+ const baseURL = props.env === 'dev'
60
+ ? 'http://localhost:4800'
61
+ : 'https://msi.dedot.io'
62
+
63
+ wioRef.current.initiate( webViewRef, baseURL )
64
+
65
+ // Setup event listeners
66
+ wioRef.current
67
+ .once('connect', () => {
68
+ const wio = wioRef.current!
69
+
70
+ // Bind with access token and origin
71
+ wio.emit('bind', {
72
+ ...props,
73
+ origin: 'react-native'
74
+ }, ( error: string | boolean ) => {
75
+ if( error ){
76
+ const errorObj = new Error( typeof error === 'string' ? error : 'Connection failed' )
77
+ setHasError( true )
78
+ props.onError?.( errorObj )
79
+ return
80
+ }
81
+
82
+ setIsConnected( true )
83
+ })
84
+ })
85
+ .on('error', ( error: Error | string ) => {
86
+ const errorObj = typeof error === 'object' ? error : new Error( error )
87
+ setHasError( true )
88
+ props.onError?.( errorObj )
89
+ })
90
+ .on('ready', () => {
91
+ console.log('MSI ready')
92
+ props.onReady?.()
93
+
94
+ // Initialize API
95
+ if( wioRef.current && isConnected && !controlsRef.current ){
96
+ const
97
+ controls = new Controls( wioRef.current, props ),
98
+ handles = new Handles( wioRef.current, controls, props ),
99
+ plugins = new Plugins( wioRef.current, handles, controls, props )
100
+
101
+ plugins.mount( REGISTERED_PLUGINS )
102
+
103
+ controlsRef.current = controls
104
+ handlesRef.current = handles
105
+ pluginsRef.current = plugins
106
+
107
+ props.onLoaded?.({
108
+ controls,
109
+ handles,
110
+ plugins
111
+ })
112
+ }
113
+ })
114
+
115
+ // Handle app state changes
116
+ const subscription = AppState.addEventListener('change', ( nextAppState: AppStateStatus ) => {
117
+ if( nextAppState === 'background' ){
118
+ // Pause updates when app goes to background
119
+ console.log('App backgrounded - pausing map updates')
120
+ }
121
+ else if( nextAppState === 'active' ){
122
+ // Resume when app comes to foreground
123
+ console.log('App active - resuming map updates')
124
+ }
125
+ })
126
+
127
+ return () => {
128
+ subscription.remove()
129
+ wioRef.current?.disconnect()
130
+ }
131
+ }, [])
132
+
133
+ const handleMessage = ( event: any ) => {
134
+ wioRef.current?.handleMessage( event )
135
+ }
136
+
137
+ const
138
+ baseURL = props.env === 'dev'
139
+ ? 'http://localhost:4800'
140
+ : 'https://msi.dedot.io',
141
+ mapUrl = `${baseURL}?token=${props.accessToken}&v=${props.version || 1}`
142
+
143
+ return (
144
+ <View style={styles.container}>
145
+ <WebView
146
+ ref={webViewRef}
147
+ source={{ uri: mapUrl }}
148
+ style={styles.webview}
149
+ onMessage={handleMessage}
150
+ injectedJavaScript={wioRef.current?.getInjectedJavaScript()}
151
+ javaScriptEnabled={true}
152
+ domStorageEnabled={true}
153
+ geolocationEnabled={true}
154
+ allowsInlineMediaPlayback={true}
155
+ cacheEnabled={true}
156
+ androidLayerType="hardware"
157
+ allowsBackForwardNavigationGestures={false}
158
+ bounces={false}
159
+ scrollEnabled={false}
160
+ mixedContentMode="always"
161
+ originWhitelist={['*']}
162
+ onLoadStart={() => console.log('WebView loading...')}
163
+ onLoadEnd={() => {
164
+ console.log('WebView loaded')
165
+ setTimeout(() => wioRef.current?.emit('ping'), 300 )
166
+ }}
167
+ onError={( syntheticEvent ) => {
168
+ const { nativeEvent } = syntheticEvent
169
+ console.error('WebView error:', nativeEvent )
170
+ setHasError( true )
171
+ props.onError?.( new Error( nativeEvent.description || 'WebView error' ) )
172
+ }}
173
+ />
174
+
175
+ {hasError && (
176
+ <View style={styles.errorOverlay}>
177
+ <View style={styles.errorCard}>
178
+ {/* Error UI can be added here */}
179
+ </View>
180
+ </View>
181
+ )}
182
+ </View>
183
+ )
184
+ })
185
+
186
+ const styles = StyleSheet.create({
187
+ container: {
188
+ flex: 1,
189
+ backgroundColor: '#f0f0f0'
190
+ },
191
+ webview: {
192
+ flex: 1,
193
+ backgroundColor: 'transparent'
194
+ },
195
+ errorOverlay: {
196
+ ...StyleSheet.absoluteFillObject,
197
+ backgroundColor: 'rgba(0,0,0,0.5)',
198
+ justifyContent: 'center',
199
+ alignItems: 'center'
200
+ },
201
+ errorCard: {
202
+ backgroundColor: 'white',
203
+ padding: 20,
204
+ borderRadius: 12,
205
+ margin: 20
206
+ }
207
+ })
208
+
209
+ // =====================================================
210
+ // MSI CLASS
211
+ // =====================================================
212
+
213
+ export class MSIClass {
214
+ private options: MapOptions
215
+ public onReady?: () => void
216
+ public onError?: (error: Error) => void
217
+ public onLoaded?: (msi: MSIInterface) => void
218
+
219
+ constructor( options: MapOptions ){
220
+ this.options = options
221
+
222
+ if( !this.options.accessToken )
223
+ throw new Error('Invalid Access Token')
224
+ }
225
+
226
+ plugin<T>( name: string, fn: Plugin<T> ){
227
+ REGISTERED_PLUGINS[name] = fn
228
+ }
229
+
230
+ once( event: string, handler: Function ){
231
+ if( event === 'ready' ) this.onReady = handler as any
232
+ else if( event === 'error' ) this.onError = handler as any
233
+ return this
234
+ }
235
+
236
+ on( event: string, handler: Function ){
237
+ return this.once( event, handler )
238
+ }
239
+
240
+ getOptions(): MapOptions {
241
+ return this.options
242
+ }
243
+
244
+ load(): Promise<MSIInterface> {
245
+ return new Promise(( resolve, reject ) => {
246
+ this.onLoaded = resolve
247
+ this.onError = reject
248
+ })
249
+ }
250
+
251
+ isReady(): boolean {
252
+ return false
253
+ }
254
+ }
@@ -0,0 +1,14 @@
1
+ import MSI from './MSI'
2
+ import Utils from '../utils'
3
+
4
+ import Order from './DClient/Order'
5
+ import Event from './DClient/Event'
6
+ import Client from './DClient/Client'
7
+
8
+ const DClient = { Client, Order, Event }
9
+
10
+ export default {
11
+ MSI,
12
+ Utils,
13
+ DClient
14
+ }