react-native-pointr 10.7.1 → 10.9.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/API_REFERENCE.md CHANGED
@@ -354,6 +354,138 @@ const clientName = await pointrSdk.getClientName();
354
354
 
355
355
  ---
356
356
 
357
+ The Site Manager and Data Manager methods that follow resolve sites and buildings by either identifier, defaulting to the **internal** one — whereas the older `getPois`, `getBuildings`, and `getSiteByExternalId` above always take an **external** identifier.
358
+
359
+ #### `getSite(siteId, isExternalIdentifier?)`
360
+
361
+ Returns a `Promise<PTRSite | null>`, or `null` if the site is not found.
362
+
363
+ **Parameters:**
364
+ - `siteId` (`string`): Site identifier.
365
+ - `isExternalIdentifier` (`boolean`, default `false`): When `false`, `siteId` is the internal identifier; when `true`, the external identifier.
366
+
367
+ ```typescript
368
+ const site = await pointrSdk.getSite('<SITE_ID>');
369
+ const byExternal = await pointrSdk.getSite('<SITE_EXTERNAL_ID>', true);
370
+ ```
371
+
372
+ ---
373
+
374
+ #### `getSiteBuildings(siteId, isExternalIdentifier?)`
375
+
376
+ Returns a `Promise<PTRBuilding[]>` with all buildings for the given site. Rejects with `SITE_NOT_FOUND` if the site does not exist.
377
+
378
+ **Parameters:**
379
+ - `siteId` (`string`): Site identifier.
380
+ - `isExternalIdentifier` (`boolean`, default `false`): Resolve `siteId` as internal (`false`) or external (`true`).
381
+
382
+ ```typescript
383
+ const buildings = await pointrSdk.getSiteBuildings('<SITE_ID>');
384
+ ```
385
+
386
+ ---
387
+
388
+ #### `getBuilding(siteId, buildingId, isExternalIdentifier?)`
389
+
390
+ Returns a `Promise<PTRBuilding | null>`, or `null` if the building is not found in that site. Building identifiers are only unique within a site, so the lookup is always scoped by `siteId`.
391
+
392
+ Rejects with `NOT_READY` if the SDK is not running, or `ERROR` if the lookup fails.
393
+
394
+ **Parameters:**
395
+ - `siteId` (`string`): Site identifier.
396
+ - `buildingId` (`string`): Building identifier.
397
+ - `isExternalIdentifier` (`boolean`, default `false`): When `false`, **both** ids are internal; when `true`, **both** are external.
398
+
399
+ ```typescript
400
+ const building = await pointrSdk.getBuilding('<SITE_ID>', '<BUILDING_ID>');
401
+ ```
402
+
403
+ ---
404
+
405
+ #### `getLevelByExternalIdentifier(buildingExternalIdentifier, levelExternalIdentifier)`
406
+
407
+ Returns a `Promise<PTRLevel | null>`, or `null` if the level is not found in that building.
408
+
409
+ Level external identifiers are only unique **within a building**, so the building's external identifier is required to scope the lookup. Both native SDKs expose this lookup by external identifier only, so there is no `isExternalIdentifier` flag — pass external identifiers for both arguments.
410
+
411
+ **Parameters:**
412
+ - `buildingExternalIdentifier` (`string`): External identifier of the building containing the level.
413
+ - `levelExternalIdentifier` (`string`): External identifier of the level.
414
+
415
+ ```typescript
416
+ const level = await pointrSdk.getLevelByExternalIdentifier(
417
+ '<BUILDING_EXTERNAL_ID>',
418
+ '<LEVEL_EXTERNAL_ID>'
419
+ );
420
+ // level?.index — 0 is ground level
421
+ ```
422
+
423
+ ---
424
+
425
+ #### `getMapUrl(siteId, isExternalIdentifier?)`
426
+
427
+ Returns a `Promise<string | null>` with the site's map data URL, or `null` if unavailable. Rejects with `SITE_NOT_FOUND` if the site does not exist, or `ERROR` if the URL cannot be retrieved.
428
+
429
+ **Parameters:**
430
+ - `siteId` (`string`): Site identifier.
431
+ - `isExternalIdentifier` (`boolean`, default `false`): Resolve `siteId` as internal (`false`) or external (`true`).
432
+
433
+ ---
434
+
435
+ #### `getStyleJsonUrl()`
436
+
437
+ Returns a `Promise<string | null>` with the map style JSON URL, or `null` if unavailable.
438
+
439
+ ---
440
+
441
+ #### `loadDataForSite(siteId, shouldRespectCachePolicy?, isExternalIdentifier?)`
442
+
443
+ Starts data management for a site if the data is not already present. Returns a `Promise<void>`.
444
+
445
+ This resolves as soon as data management has been **triggered**, not when it completes — subscribe to `onDataManagerCompleteAll` (which reports failures) or `onDataManagerReady` to observe the outcome.
446
+
447
+ **Parameters:**
448
+ - `siteId` (`string`): Site identifier.
449
+ - `shouldRespectCachePolicy` (`boolean`, default `true`): When `true`, waits until the cache expires if data is already present. When `false`, ignores the cache and triggers an update immediately.
450
+ - `isExternalIdentifier` (`boolean`, default `false`): Resolve `siteId` as internal (`false`) or external (`true`).
451
+
452
+ Note that if the data is already present no event may fire at all, so check `isSiteContentReady` first and guard with a timeout:
453
+
454
+ ```typescript
455
+ if (await pointrSdk.isSiteContentReady(siteId)) return; // nothing to download
456
+
457
+ let subscription: { remove: () => void } | null = null;
458
+ let timeout: ReturnType<typeof setTimeout> | null = null;
459
+ const cleanup = () => {
460
+ subscription?.remove();
461
+ if (timeout != null) clearTimeout(timeout);
462
+ };
463
+
464
+ subscription = pointrSdk.onDataManagerCompleteAll((event) => {
465
+ if (event.site.identifier !== siteId) return;
466
+ cleanup();
467
+ console.log(event.isSuccessful ? 'loaded' : event.errors.join(', '));
468
+ });
469
+ timeout = setTimeout(() => {
470
+ cleanup();
471
+ console.log('timed out');
472
+ }, 60000);
473
+
474
+ await pointrSdk.loadDataForSite(siteId);
475
+ ```
476
+
477
+ ---
478
+
479
+ #### `isSiteContentReady(siteId, isExternalIdentifier?)`
480
+
481
+ Returns a `Promise<boolean>` — whether all data needed to display the site is available. This does not mean the data is the latest, only that there is valid data to show.
482
+
483
+ **Parameters:**
484
+ - `siteId` (`string`): Site identifier.
485
+ - `isExternalIdentifier` (`boolean`, default `false`): Resolve `siteId` as internal (`false`) or external (`true`).
486
+
487
+ ---
488
+
357
489
  #### `isWayfindingReady(siteId)`
358
490
 
359
491
  Returns a `Promise<boolean>` indicating whether wayfinding data is ready for the given site.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [10.9.0] - 2026-09-01
8
+
9
+ ### Changed
10
+ - Mobile SDK 10.9.0 integration.
11
+
12
+
13
+ ## [10.8.0] - 2026-08-18
14
+
15
+ ### Changed
16
+ - Mobile SDK 10.8.0 integration.
17
+
18
+
7
19
  ## [10.7.1] - 2026-08-05
8
20
 
9
21
  ### Changed
@@ -39,6 +51,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
39
51
  ### Added
40
52
  - **`PointrSdk` methods** exposing previously native-only capabilities: `shouldRequestPermissionsAtStartup`, `requestPermissions`, `getPois`, `searchPois`, `getSites`, `getBuildings`, `getSiteByExternalId`, `getClientName`, `isWayfindingReady`, `calculateDistance`, `isMyCarMarked`.
41
53
  - **Shared bridge key constants** — every string crossing the JS ↔ native boundary for a map widget action (action types, `action` and `sdkConfig` payload keys, view manager command names) is now declared in one constants file per platform: `src/constants/bridgeKeys.ts`, `android/src/main/java/com/pointr/PTRBridgeKeys.kt` and `ios/PTRBridgeKeys.swift`. No literals remain in the action serializer or in either native reader, so a key can no longer be renamed on one side only. `PTRActionParamKeys`, `PTRSdkConfigKeys` and `PTRCommandNames` are exported from the package.
54
+ - **SiteManager** exposed through `PointrSdk`: `getSite`, `getSiteBuildings`, `getBuilding`, `getLevelByExternalIdentifier`, `getMapUrl`, `getStyleJsonUrl`. Site and building lookups accept an `isExternalIdentifier` flag to resolve by internal (default) or external identifier.
55
+ - **Level external identifiers** — `getLevelByExternalIdentifier(buildingExternalIdentifier, levelExternalIdentifier)` wraps the level lookup added in native SDK 10.6.0. Levels are only unique within a building, so the building's external identifier scopes the lookup. Exposed by external identifier only, matching the native SDKs.
56
+ - **DataManager** exposed through `PointrSdk`: `loadDataForSite`, `isSiteContentReady`, plus the five data-management events (`onDataManagerStart`, `onDataManagerCompleteAll`, `onDataManagerBeginProcessing`, `onDataManagerEndProcessing`, `onDataManagerReady`).
57
+ - **Hooks**: `usePointrSite`, `usePointrData`, and one hook per data-manager event.
58
+ - **Bridge key constants for the native → JS path** — the event payload fields (`PTREventPayloadKeys`) and the site / building / level object fields (`PTRModelKeys`) join the existing groups in the three `PTRBridgeKeys` files, so the data-manager events and the level lookup no longer write raw literals on either native side. iOS event names moved to `PTRNativeLibrary.EventNames`, matching Android's companion object and `PTREvents` in TypeScript.
59
+
42
60
 
43
61
  ### Changed
44
62
  - **Actions serialize through `toPayload()`** — `PTRAction` subclasses now build their JSON payload from `PTRActionParamKeys` instead of the component spreading their class fields, so a field rename can no longer silently change the wire format.
@@ -48,7 +66,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
48
66
  - **`markMyCar` / `showMyCar` via the declarative `action` prop on Android** — the completion event read an argument slot the declarative path never pushed, throwing inside the callback. Argument reads are now type-checked with defaults.
49
67
  - **`personaId` in the `sdkConfig` prop on iOS** — the key was written by JS but never read, so the persona was ignored when the widget initialised the SDK.
50
68
 
51
-
52
69
  ## [10.3.0] - 2026-06-30
53
70
 
54
71
  ### Changed
package/EXTENDING.md CHANGED
@@ -360,7 +360,7 @@ Ensure both iOS and Android implementations return the same data structure:
360
360
 
361
361
  ### 7. Never Hard-code Bridge Strings
362
362
 
363
- Map widget actions cross the bridge as JSON, so a key that JS writes and native
363
+ Data crosses the bridge as JSON, so a key that one side writes and the other
364
364
  reads is matched by string only — rename it on one side and the other side
365
365
  silently reads an empty value, with no compile error anywhere. Every such string
366
366
  lives in a constants file, one per language, and the three are kept identical:
@@ -371,12 +371,22 @@ lives in a constants file, one per language, and the three are kept identical:
371
371
  | Android | `android/src/main/java/com/pointr/PTRBridgeKeys.kt` |
372
372
  | iOS | `ios/PTRBridgeKeys.swift` |
373
373
 
374
- They cover four groups:
374
+ They cover six groups — the first four on the JS -> native path, the last two on
375
+ the native -> JS path:
375
376
 
376
377
  - **Action types** — the `type` discriminator of the `action` prop
377
378
  - **Action param keys** — every other field of the `action` prop payload
378
379
  - **SDK config keys** — the fields of the `sdkConfig` prop
379
380
  - **Command names** — the view manager commands `executeMapAction` dispatches
381
+ - **Event payload keys** — the fields of the payloads native emits to JS; the
382
+ interfaces in `src/types/events.ts` are the TypeScript side of this contract
383
+ - **Model keys** — the fields of the site / building / level objects native
384
+ serializes, matching `src/types/PTRSite.ts`
385
+
386
+ Event *names* are the one exception: each side declares them where it emits or
387
+ subscribes — `PTREvents` in `src/constants/index.ts`, `PTRNativeLibrary`'s
388
+ companion object on Android and `PTRNativeLibrary.EventNames` on iOS. They must
389
+ still match across all three.
380
390
 
381
391
  When adding an action or a parameter:
382
392
 
@@ -93,7 +93,7 @@ dependencies {
93
93
  //noinspection GradleDynamicVersion
94
94
  implementation "com.facebook.react:react-android:0.82.1"
95
95
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
96
- implementation("com.pointrlabs:pointr:10.7.0")
96
+ implementation("com.pointrlabs:pointr:10.9.0")
97
97
  implementation ("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
98
98
  implementation 'com.google.android.material:material:1.12.0'
99
99
  implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
@@ -3,9 +3,10 @@ package com.pointr
3
3
  import androidx.annotation.StringDef
4
4
 
5
5
  /**
6
- * Every string that crosses the JS <-> native boundary when an action is sent
7
- * to the map widget: action type discriminators, `action` / `sdkConfig` payload
8
- * keys and view manager command names.
6
+ * Every string that crosses the JS <-> native boundary: action type
7
+ * discriminators, `action` / `sdkConfig` payload keys and view manager command
8
+ * names on the JS -> native path, plus the payload keys of the events native
9
+ * emits back to JS.
9
10
  *
10
11
  * Nothing here may be hard-coded anywhere else — a key renamed on one side only
11
12
  * fails silently (`optString` returns an empty string), so all three sides read
@@ -115,6 +116,47 @@ object PTRBridgeKeys {
115
116
  /** Highlight a category */
116
117
  const val HIGHLIGHT_CATEGORY = "highlightCategory"
117
118
  }
119
+
120
+ /**
121
+ * Field names of the event payloads emitted to JS through the event
122
+ * emitter. Native writes them, JS reads them back through the typed
123
+ * interfaces in `src/types/events.ts`.
124
+ *
125
+ * Event names are not listed here: they live in `PTREvents` on the JS side
126
+ * and in [PTRNativeLibrary]'s companion object on this side.
127
+ */
128
+ object EventPayloadKeys {
129
+ /** Site the event refers to, serialized by [ModelKeys] */
130
+ const val SITE = "site"
131
+ /** Whether the data came from the server (online) or a local bundle */
132
+ const val IS_ONLINE_DATA = "isOnlineData"
133
+ /** Whether the reported operation succeeded */
134
+ const val IS_SUCCESSFUL = "isSuccessful"
135
+ /** Error messages collected during the operation */
136
+ const val ERRORS = "errors"
137
+ /** Data type being processed */
138
+ const val DATA_TYPE = "dataType"
139
+ /** Numeric value of `dataType` */
140
+ const val DATA_TYPE_VALUE = "value"
141
+ /** Human-readable name of `dataType` */
142
+ const val DATA_TYPE_NAME = "name"
143
+ }
144
+
145
+ /**
146
+ * Field names shared by the site, building and level objects serialized for
147
+ * JS — both as promise results and inside event payloads. They match the
148
+ * interfaces in `src/types/PTRSite.ts`.
149
+ */
150
+ object ModelKeys {
151
+ /** Internal identifier */
152
+ const val IDENTIFIER = "identifier"
153
+ /** External (customer-facing) identifier */
154
+ const val EXTERNAL_IDENTIFIER = "externalIdentifier"
155
+ /** Human-readable name */
156
+ const val NAME = "name"
157
+ /** Zero-based level index */
158
+ const val INDEX = "index"
159
+ }
118
160
  }
119
161
 
120
162
  /** Restricts a String parameter to the [PTRBridgeKeys.ActionTypes] values. */
@@ -12,16 +12,22 @@ import com.facebook.react.bridge.Promise
12
12
  import com.facebook.react.bridge.ReactApplicationContext
13
13
  import com.facebook.react.bridge.ReactContextBaseJavaModule
14
14
  import com.facebook.react.bridge.ReactMethod
15
+ import com.facebook.react.bridge.WritableArray
15
16
  import com.facebook.react.bridge.WritableMap
16
17
  import com.facebook.react.modules.core.DeviceEventManagerModule
18
+ import com.pointr.PTRBridgeKeys.EventPayloadKeys
19
+ import com.pointr.PTRBridgeKeys.ModelKeys
17
20
  import com.pointrlabs.core.geofence.GeofenceEvent
18
21
  import com.pointrlabs.core.geofence.GeofenceEventType
19
22
  import com.pointrlabs.core.geofence.GeofenceType
23
+ import com.pointrlabs.core.management.DataManager
20
24
  import com.pointrlabs.core.management.GeofenceManager
21
25
  import com.pointrlabs.core.management.Pointr
22
26
  import com.pointrlabs.core.management.PositioningManager
23
27
  import com.pointrlabs.core.management.interfaces.PointrListener
24
28
  import com.pointrlabs.core.management.models.Building
29
+ import com.pointrlabs.core.management.models.DataType
30
+ import com.pointrlabs.core.management.models.ErrorMessage
25
31
  import com.pointrlabs.core.management.models.PTRParams
26
32
  import com.pointrlabs.core.management.models.Site
27
33
  import com.pointrlabs.core.nativecore.wrappers.Plog
@@ -43,6 +49,7 @@ class PTRNativeLibrary(reactContext: ReactApplicationContext) :
43
49
 
44
50
  private val positionManagerListener: PositioningManager.Listener
45
51
  private val geofenceListener : GeofenceManager.Listener
52
+ private val dataManagerListener: DataManager.Listener
46
53
  private var listenerCount = 0
47
54
 
48
55
  init {
@@ -58,9 +65,75 @@ class PTRNativeLibrary(reactContext: ReactApplicationContext) :
58
65
  sendEventToJs(reactContext, OnGeofenceEvent, params)
59
66
  }
60
67
  }
68
+ dataManagerListener = object : DataManager.Listener {
69
+ override fun onDataManagerStartDataManagementForSite(site: Site, isOnlineData: Boolean) {
70
+ sendEventToJs(reactContext, OnDataManagerStartDataManagementForSite, Arguments.createMap().apply {
71
+ putMap(EventPayloadKeys.SITE, site.toWritableMap())
72
+ putBoolean(EventPayloadKeys.IS_ONLINE_DATA, isOnlineData)
73
+ })
74
+ }
75
+
76
+ override fun onDataManagerCompleteAllForSite(
77
+ site: Site,
78
+ isSuccessful: Boolean,
79
+ isOnlineData: Boolean,
80
+ errors: List<ErrorMessage?>?
81
+ ) {
82
+ sendEventToJs(reactContext, OnDataManagerCompleteAllForSite, Arguments.createMap().apply {
83
+ putMap(EventPayloadKeys.SITE, site.toWritableMap())
84
+ putBoolean(EventPayloadKeys.IS_SUCCESSFUL, isSuccessful)
85
+ putBoolean(EventPayloadKeys.IS_ONLINE_DATA, isOnlineData)
86
+ putArray(EventPayloadKeys.ERRORS, errorsArray(errors))
87
+ })
88
+ }
89
+
90
+ override fun onDataManagerBeginProcessingDataForSite(site: Site, dataType: DataType?, isOnlineData: Boolean) {
91
+ sendEventToJs(reactContext, OnDataManagerBeginProcessingDataForSite, Arguments.createMap().apply {
92
+ putMap(EventPayloadKeys.SITE, site.toWritableMap())
93
+ putMap(EventPayloadKeys.DATA_TYPE, dataTypeMap(dataType))
94
+ putBoolean(EventPayloadKeys.IS_ONLINE_DATA, isOnlineData)
95
+ })
96
+ }
97
+
98
+ override fun onDataManagerEndProcessingDataForSite(
99
+ site: Site,
100
+ dataType: DataType?,
101
+ isOnlineData: Boolean,
102
+ isSuccessful: Boolean,
103
+ errors: List<ErrorMessage?>?
104
+ ) {
105
+ sendEventToJs(reactContext, OnDataManagerEndProcessingDataForSite, Arguments.createMap().apply {
106
+ putMap(EventPayloadKeys.SITE, site.toWritableMap())
107
+ putMap(EventPayloadKeys.DATA_TYPE, dataTypeMap(dataType))
108
+ putBoolean(EventPayloadKeys.IS_ONLINE_DATA, isOnlineData)
109
+ putBoolean(EventPayloadKeys.IS_SUCCESSFUL, isSuccessful)
110
+ putArray(EventPayloadKeys.ERRORS, errorsArray(errors))
111
+ })
112
+ }
113
+
114
+ override fun onDataManagerReadyForSite(site: Site) {
115
+ sendEventToJs(reactContext, OnDataManagerReadyForSite, Arguments.createMap().apply {
116
+ putMap(EventPayloadKeys.SITE, site.toWritableMap())
117
+ })
118
+ }
119
+ }
61
120
  setPointrListeners()
62
121
  }
63
122
 
123
+ private fun dataTypeMap(dataType: DataType?): WritableMap? {
124
+ val type = dataType ?: return null
125
+ return Arguments.createMap().apply {
126
+ putInt(EventPayloadKeys.DATA_TYPE_VALUE, type.value)
127
+ putString(EventPayloadKeys.DATA_TYPE_NAME, type.name)
128
+ }
129
+ }
130
+
131
+ private fun errorsArray(errors: List<ErrorMessage?>?): WritableArray {
132
+ val arr = Arguments.createArray()
133
+ errors?.filterNotNull()?.forEach { arr.pushString(it.message) }
134
+ return arr
135
+ }
136
+
64
137
 
65
138
  override fun getName(): String {
66
139
  return NAME
@@ -198,6 +271,149 @@ class PTRNativeLibrary(reactContext: ReactApplicationContext) :
198
271
  }
199
272
  }
200
273
 
274
+ // ─── Data Manager ────────────────────────────────────────────────────────
275
+
276
+ @ReactMethod
277
+ fun loadDataForSite(
278
+ siteId: String,
279
+ shouldRespectCachePolicy: Boolean,
280
+ isExternalIdentifier: Boolean,
281
+ promise: Promise
282
+ ) {
283
+ try {
284
+ val site = resolveSite(siteId, isExternalIdentifier)
285
+ ?: return promise.reject("SITE_NOT_FOUND", "Site not found: $siteId")
286
+ Pointr.getPointr()?.dataManager?.loadDataForSite(site, shouldRespectCachePolicy)
287
+ promise.resolve(null)
288
+ } catch (e: Exception) {
289
+ promise.reject("ERROR", "Failed to load data for site: ${e.message}", e)
290
+ }
291
+ }
292
+
293
+ @ReactMethod
294
+ fun isSiteContentReady(siteId: String, isExternalIdentifier: Boolean, promise: Promise) {
295
+ try {
296
+ val site = resolveSite(siteId, isExternalIdentifier)
297
+ ?: return promise.reject("SITE_NOT_FOUND", "Site not found: $siteId")
298
+ val ready = Pointr.getPointr()?.dataManager?.isSiteContentReady(site) ?: false
299
+ promise.resolve(ready)
300
+ } catch (e: Exception) {
301
+ promise.reject("ERROR", "Failed to check site content: ${e.message}", e)
302
+ }
303
+ }
304
+
305
+ private fun resolveSite(siteId: String, isExternalIdentifier: Boolean): Site? {
306
+ val siteManager = Pointr.getPointr()?.siteManager ?: return null
307
+ return if (isExternalIdentifier) {
308
+ siteManager.getSiteByExternalIdentifier(siteId)
309
+ } else {
310
+ siteManager.getSite(siteId)
311
+ }
312
+ }
313
+
314
+ // ─── Site Manager ────────────────────────────────────────────────────────
315
+
316
+ @ReactMethod
317
+ fun getSite(siteId: String, isExternalIdentifier: Boolean, promise: Promise) {
318
+ try {
319
+ val site = resolveSite(siteId, isExternalIdentifier)
320
+ promise.resolve(site?.toWritableMap())
321
+ } catch (e: Exception) {
322
+ promise.reject("ERROR", "Failed to get site: ${e.message}", e)
323
+ }
324
+ }
325
+
326
+ @ReactMethod
327
+ fun getSiteBuildings(siteId: String, isExternalIdentifier: Boolean, promise: Promise) {
328
+ try {
329
+ val site = resolveSite(siteId, isExternalIdentifier)
330
+ ?: return promise.reject("SITE_NOT_FOUND", "Site not found: $siteId")
331
+ val buildings = Pointr.getPointr()?.siteManager?.getBuildings(site.identifier) ?: emptyList()
332
+ val arr = Arguments.createArray()
333
+ buildings.forEach { b -> arr.pushMap(b.toWritableMap()) }
334
+ promise.resolve(arr)
335
+ } catch (e: Exception) {
336
+ promise.reject("ERROR", "Failed to get buildings: ${e.message}", e)
337
+ }
338
+ }
339
+
340
+ @ReactMethod
341
+ fun getBuilding(
342
+ siteId: String,
343
+ buildingId: String,
344
+ isExternalIdentifier: Boolean,
345
+ promise: Promise
346
+ ) {
347
+ try {
348
+ val siteManager = Pointr.getPointr()?.siteManager
349
+ ?: return promise.reject("NOT_READY", "Site manager is not available")
350
+ val building = if (isExternalIdentifier) {
351
+ siteManager.getBuildingByExternalIdentifier(siteId, buildingId)
352
+ } else {
353
+ siteManager.getBuilding(siteId, buildingId)
354
+ }
355
+ promise.resolve(building?.toWritableMap())
356
+ } catch (e: Exception) {
357
+ promise.reject("ERROR", "Failed to get building: ${e.message}", e)
358
+ }
359
+ }
360
+
361
+ @ReactMethod
362
+ fun getLevelByExternalIdentifier(
363
+ buildingExternalIdentifier: String,
364
+ levelExternalIdentifier: String,
365
+ promise: Promise
366
+ ) {
367
+ try {
368
+ val siteManager = Pointr.getPointr()?.siteManager
369
+ ?: return promise.reject("NOT_READY", "Site manager is not available")
370
+ // Named arguments on purpose: PointrKit takes (level, building) while
371
+ // this SDK takes (building, level), and both are plain strings.
372
+ val level = siteManager.getLevelByExternalIdentifier(
373
+ buildingExternalIdentifier = buildingExternalIdentifier,
374
+ levelExternalIdentifier = levelExternalIdentifier
375
+ )
376
+ promise.resolve(level?.let { lvl ->
377
+ Arguments.createMap().apply {
378
+ putString(ModelKeys.IDENTIFIER, lvl.identifier)
379
+ putString(ModelKeys.EXTERNAL_IDENTIFIER, lvl.externalIdentifier)
380
+ putString(ModelKeys.NAME, lvl.name ?: "")
381
+ putInt(ModelKeys.INDEX, lvl.index)
382
+ }
383
+ })
384
+ } catch (e: Exception) {
385
+ promise.reject("ERROR", "Failed to get level: ${e.message}", e)
386
+ }
387
+ }
388
+
389
+ @ReactMethod
390
+ fun getMapUrl(siteId: String, isExternalIdentifier: Boolean, promise: Promise) {
391
+ try {
392
+ val site = resolveSite(siteId, isExternalIdentifier)
393
+ ?: return promise.reject("SITE_NOT_FOUND", "Site not found: $siteId")
394
+ val siteManager = Pointr.getPointr()?.siteManager
395
+ ?: return promise.reject("NOT_READY", "Site manager is not available")
396
+ siteManager.getMapUrl(site.identifier) { mapUrl, error ->
397
+ if (error != null) {
398
+ promise.reject("ERROR", error)
399
+ } else {
400
+ promise.resolve(mapUrl)
401
+ }
402
+ }
403
+ } catch (e: Exception) {
404
+ promise.reject("ERROR", "Failed to get map url: ${e.message}", e)
405
+ }
406
+ }
407
+
408
+ @ReactMethod
409
+ fun getStyleJsonUrl(promise: Promise) {
410
+ try {
411
+ promise.resolve(Pointr.getPointr()?.siteManager?.styleJsonUrl)
412
+ } catch (e: Exception) {
413
+ promise.reject("ERROR", "Failed to get style json url: ${e.message}", e)
414
+ }
415
+ }
416
+
201
417
  // ─── Sites & Buildings ───────────────────────────────────────────────────
202
418
 
203
419
  @ReactMethod
@@ -358,6 +574,12 @@ class PTRNativeLibrary(reactContext: ReactApplicationContext) :
358
574
 
359
575
  const val OnGeofenceEvent = "OnGeofenceEvent"
360
576
 
577
+ const val OnDataManagerStartDataManagementForSite = "OnDataManagerStartDataManagementForSite"
578
+ const val OnDataManagerCompleteAllForSite = "OnDataManagerCompleteAllForSite"
579
+ const val OnDataManagerBeginProcessingDataForSite = "OnDataManagerBeginProcessingDataForSite"
580
+ const val OnDataManagerEndProcessingDataForSite = "OnDataManagerEndProcessingDataForSite"
581
+ const val OnDataManagerReadyForSite = "OnDataManagerReadyForSite"
582
+
361
583
  var shouldRequestPermissionsAtStartup = true
362
584
 
363
585
  fun sendEventToJs(
@@ -597,6 +819,7 @@ class PTRNativeLibrary(reactContext: ReactApplicationContext) :
597
819
  if (pointr.state != Pointr.State.RUNNING) return
598
820
  pointr.positioningManager?.removeListener(positionManagerListener)
599
821
  pointr.geofenceManager?.removeListener(geofenceListener)
822
+ pointr.dataManager?.removeListener(dataManagerListener)
600
823
  }
601
824
 
602
825
  private fun setPointrListeners() {
@@ -604,6 +827,7 @@ class PTRNativeLibrary(reactContext: ReactApplicationContext) :
604
827
  if (pointr.state != Pointr.State.RUNNING) return
605
828
  pointr.positioningManager?.addListener(positionManagerListener)
606
829
  pointr.geofenceManager?.addListener(geofenceListener)
830
+ pointr.dataManager?.addListener(dataManagerListener)
607
831
  if (shouldRequestPermissionsAtStartup) {
608
832
  requestPermissions()
609
833
  } else {
@@ -1,8 +1,9 @@
1
1
  import Foundation
2
2
 
3
- /// Every string that crosses the JS <-> native boundary when an action is sent
4
- /// to the map widget: action type discriminators, `action` / `sdkConfig` payload
5
- /// keys and view manager command names.
3
+ /// Every string that crosses the JS <-> native boundary: action type
4
+ /// discriminators, `action` / `sdkConfig` payload keys and view manager command
5
+ /// names on the JS -> native path, plus the payload keys of the events native
6
+ /// emits back to JS.
6
7
  ///
7
8
  /// Nothing here may be hard-coded anywhere else — a key renamed on one side only
8
9
  /// fails silently (the cast yields `nil` and the parameter defaults to empty), so
@@ -107,4 +108,41 @@ enum PTRBridgeKeys {
107
108
  /// Highlight a category
108
109
  static let highlightCategory = "highlightCategory"
109
110
  }
111
+
112
+ /// Field names of the event payloads emitted to JS through the event
113
+ /// emitter. Native writes them, JS reads them back through the typed
114
+ /// interfaces in `src/types/events.ts`.
115
+ ///
116
+ /// Event names are not listed here: they live in `PTREvents` on the JS side
117
+ /// and in `PTRNativeLibrary.EventNames` on this side.
118
+ enum EventPayloadKeys {
119
+ /// Site the event refers to, serialized by `ModelKeys`
120
+ static let site = "site"
121
+ /// Whether the data came from the server (online) or a local bundle
122
+ static let isOnlineData = "isOnlineData"
123
+ /// Whether the reported operation succeeded
124
+ static let isSuccessful = "isSuccessful"
125
+ /// Error messages collected during the operation
126
+ static let errors = "errors"
127
+ /// Data type being processed
128
+ static let dataType = "dataType"
129
+ /// Numeric value of `dataType`
130
+ static let dataTypeValue = "value"
131
+ /// Human-readable name of `dataType`
132
+ static let dataTypeName = "name"
133
+ }
134
+
135
+ /// Field names shared by the site, building and level objects serialized for
136
+ /// JS — both as promise results and inside event payloads. They match the
137
+ /// interfaces in `src/types/PTRSite.ts`.
138
+ enum ModelKeys {
139
+ /// Internal identifier
140
+ static let identifier = "identifier"
141
+ /// External (customer-facing) identifier
142
+ static let externalIdentifier = "externalIdentifier"
143
+ /// Human-readable name
144
+ static let name = "name"
145
+ /// Zero-based level index
146
+ static let index = "index"
147
+ }
110
148
  }
@@ -35,6 +35,48 @@ RCT_EXTERN_METHOD(getPois:(NSString *)siteId
35
35
  resolver:(RCTPromiseResolveBlock)resolve
36
36
  rejecter:(RCTPromiseRejectBlock)reject)
37
37
 
38
+ // Data Manager
39
+ RCT_EXTERN_METHOD(loadDataForSite:(NSString *)siteId
40
+ shouldRespectCachePolicy:(BOOL)shouldRespectCachePolicy
41
+ isExternalIdentifier:(BOOL)isExternalIdentifier
42
+ resolver:(RCTPromiseResolveBlock)resolve
43
+ rejecter:(RCTPromiseRejectBlock)reject)
44
+
45
+ RCT_EXTERN_METHOD(isSiteContentReady:(NSString *)siteId
46
+ isExternalIdentifier:(BOOL)isExternalIdentifier
47
+ resolver:(RCTPromiseResolveBlock)resolve
48
+ rejecter:(RCTPromiseRejectBlock)reject)
49
+
50
+ // Site Manager
51
+ RCT_EXTERN_METHOD(getSite:(NSString *)siteId
52
+ isExternalIdentifier:(BOOL)isExternalIdentifier
53
+ resolver:(RCTPromiseResolveBlock)resolve
54
+ rejecter:(RCTPromiseRejectBlock)reject)
55
+
56
+ RCT_EXTERN_METHOD(getSiteBuildings:(NSString *)siteId
57
+ isExternalIdentifier:(BOOL)isExternalIdentifier
58
+ resolver:(RCTPromiseResolveBlock)resolve
59
+ rejecter:(RCTPromiseRejectBlock)reject)
60
+
61
+ RCT_EXTERN_METHOD(getBuilding:(NSString *)siteId
62
+ buildingId:(NSString *)buildingId
63
+ isExternalIdentifier:(BOOL)isExternalIdentifier
64
+ resolver:(RCTPromiseResolveBlock)resolve
65
+ rejecter:(RCTPromiseRejectBlock)reject)
66
+
67
+ RCT_EXTERN_METHOD(getLevelByExternalIdentifier:(NSString *)buildingExternalIdentifier
68
+ levelExternalIdentifier:(NSString *)levelExternalIdentifier
69
+ resolver:(RCTPromiseResolveBlock)resolve
70
+ rejecter:(RCTPromiseRejectBlock)reject)
71
+
72
+ RCT_EXTERN_METHOD(getMapUrl:(NSString *)siteId
73
+ isExternalIdentifier:(BOOL)isExternalIdentifier
74
+ resolver:(RCTPromiseResolveBlock)resolve
75
+ rejecter:(RCTPromiseRejectBlock)reject)
76
+
77
+ RCT_EXTERN_METHOD(getStyleJsonUrl:(RCTPromiseResolveBlock)resolve
78
+ rejecter:(RCTPromiseRejectBlock)reject)
79
+
38
80
  // Sites & Buildings
39
81
  RCT_EXTERN_METHOD(getSites:(RCTPromiseResolveBlock)resolve
40
82
  rejecter:(RCTPromiseRejectBlock)reject)