@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.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/allend/Access.d.ts +12 -0
- package/dist/allend/Access.d.ts.map +1 -0
- package/dist/allend/Access.js +43 -0
- package/dist/allend/Access.js.map +1 -0
- package/dist/allend/DClient/Client.d.ts +11 -0
- package/dist/allend/DClient/Client.d.ts.map +1 -0
- package/dist/allend/DClient/Client.js +50 -0
- package/dist/allend/DClient/Client.js.map +1 -0
- package/dist/allend/DClient/Event.d.ts +19 -0
- package/dist/allend/DClient/Event.d.ts.map +1 -0
- package/dist/allend/DClient/Event.js +74 -0
- package/dist/allend/DClient/Event.js.map +1 -0
- package/dist/allend/DClient/Order.d.ts +50 -0
- package/dist/allend/DClient/Order.d.ts.map +1 -0
- package/dist/allend/DClient/Order.js +321 -0
- package/dist/allend/DClient/Order.js.map +1 -0
- package/dist/allend/MSI/Controls.d.ts +232 -0
- package/dist/allend/MSI/Controls.d.ts.map +1 -0
- package/dist/allend/MSI/Controls.js +668 -0
- package/dist/allend/MSI/Controls.js.map +1 -0
- package/dist/allend/MSI/Handles.d.ts +89 -0
- package/dist/allend/MSI/Handles.d.ts.map +1 -0
- package/dist/allend/MSI/Handles.js +328 -0
- package/dist/allend/MSI/Handles.js.map +1 -0
- package/dist/allend/MSI/Plugins.d.ts +23 -0
- package/dist/allend/MSI/Plugins.d.ts.map +1 -0
- package/dist/allend/MSI/Plugins.js +29 -0
- package/dist/allend/MSI/Plugins.js.map +1 -0
- package/dist/allend/MSI/index.d.ts +38 -0
- package/dist/allend/MSI/index.d.ts.map +1 -0
- package/dist/allend/MSI/index.js +176 -0
- package/dist/allend/MSI/index.js.map +1 -0
- package/dist/allend/index.d.ts +16 -0
- package/dist/allend/index.d.ts.map +1 -0
- package/dist/allend/index.js +12 -0
- package/dist/allend/index.js.map +1 -0
- package/dist/backend/Auth.d.ts +14 -0
- package/dist/backend/Auth.d.ts.map +1 -0
- package/dist/backend/Auth.js +84 -0
- package/dist/backend/Auth.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/stream.d.ts +21 -0
- package/dist/utils/stream.d.ts.map +1 -0
- package/dist/utils/stream.js +59 -0
- package/dist/utils/stream.js.map +1 -0
- package/package.json +81 -0
- package/src/allend/Access.ts +58 -0
- package/src/allend/DClient/Client.ts +76 -0
- package/src/allend/DClient/Event.ts +76 -0
- package/src/allend/DClient/Order.ts +452 -0
- package/src/allend/MSI/Controls.ts +703 -0
- package/src/allend/MSI/Handles.ts +387 -0
- package/src/allend/MSI/Plugins.ts +52 -0
- package/src/allend/MSI/index.tsx +254 -0
- package/src/allend/index.ts +14 -0
- package/src/backend/Auth.ts +112 -0
- package/src/index.ts +18 -0
- package/src/types/access.d.ts +7 -0
- package/src/types/auth.d.ts +26 -0
- package/src/types/index.d.ts +275 -0
- package/src/utils/index.ts +4 -0
- 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
|
+
}
|