diff --git a/CHANGELOG_UNRELEASED.md b/CHANGELOG_UNRELEASED.md index 4c3882a..05c1300 100644 --- a/CHANGELOG_UNRELEASED.md +++ b/CHANGELOG_UNRELEASED.md @@ -1,8 +1,10 @@ ### Added -- We have added `SitumProvider.apiDomain`. This parameter is useful only in certain scenarios where configuring the Situm's environment is neccessary. +- Added a new callback `onInternalMapViewMessageCallback` invoked with every MapView message. + It is used internally — no action is required on your side. ### Changed -- We have simplified the autenthication process of our plugin. Use SitumProvider at the root of your app to initialize & set your `SitumProvider.apiKey`. Now this step will prevent you from calling `SitumPlugin.init()` and `SitumPlugin.setApiKey()` methods, and from specifying the `MapViewConfiguration.situmApiKey` to display our map. -- Example app: we have now simplified the authentication process in our example app, using the new `SitumProvider.apiKey`. +- Made WebView message handling more robust with guarded JSON parsing and default fallbacks for + type and payload. This is also an internal change that requires no action on your side. +- Now the MapView receives the `deviceId` from the underlying SDK. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 85e78d5..c876a33 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1681,7 +1681,7 @@ PODS: - React-logger (= 0.79.1) - React-perflogger (= 0.79.1) - React-utils (= 0.79.1) - - ReactNativeSitumPlugin (3.15.19): + - ReactNativeSitumPlugin (3.17.2): - DoubleConversion - glog - hermes-engine @@ -1705,7 +1705,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SitumSDK (= 3.34.6) + - SitumSDK (= 3.34.10) - Yoga - RNScreens (4.11.1): - DoubleConversion @@ -1756,7 +1756,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - SitumSDK (3.34.6): + - SitumSDK (3.34.10): - Protobuf (~> 3.18) - SSZipArchive (~> 2.4) - SocketRocket (0.7.1) @@ -2075,10 +2075,10 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: f3426eaf6dabae0baec543cd7bac588b6f59210c ReactCodegen: 3288d61658273d72fbd348a74b59710b9790615f ReactCommon: 9f975582dc535de1de110bdb46d4553140a77541 - ReactNativeSitumPlugin: 9919bc4478237cf3d704bd4ca958d9baa403fd37 + ReactNativeSitumPlugin: c0f365b20d0368473061abf31fa649417a98a647 RNScreens: 3dbce61975990754e4eadd42e9155d327c3445e7 RNVectorIcons: ae8e1b95f3468a360896c6242d61885efcc64241 - SitumSDK: 218abb3562cc500e638c90e68c4db7e8212c13ef + SitumSDK: de58cad6f48b83451f820452e57f1586ab8b9d2a SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef Yoga: d15f5aa644c466e917569ac43b19cbf17975239a diff --git a/example/src/screens/WayfindingScreen.tsx b/example/src/screens/WayfindingScreen.tsx index 6ec4853..2140a49 100644 --- a/example/src/screens/WayfindingScreen.tsx +++ b/example/src/screens/WayfindingScreen.tsx @@ -28,7 +28,6 @@ export const WayfindingScreen: React.FC = () => { if (!mapViewRef) { return; } - setController(mapViewRef.current); }, [mapViewRef]); @@ -36,7 +35,7 @@ export const WayfindingScreen: React.FC = () => { // The "onLoad" callback indicates that the map has been loaded and is // ready to receive calls to perform actions (e.g., selectPoi, navigateToPoi). setMapViewLoaded(true); - console.log("Situm > example > Map is ready, received event: ", event); + console.log("Situm> example> Map is ready, received event: ", event); }; // //////////////////////////////////////////////////////////////////////// @@ -45,33 +44,33 @@ export const WayfindingScreen: React.FC = () => { const onPoiSelected = (event: OnPoiSelectedResult) => { console.log( - "Situm > example > on poi selected detected: " + JSON.stringify(event) + "Situm> example> on poi selected detected: " + JSON.stringify(event) ); }; const onPoiDeselected = (event: OnPoiDeselectedResult) => { console.log( - "Situm > example > on poi deselected detected: " + JSON.stringify(event) + "Situm> example> on poi deselected detected: " + JSON.stringify(event) ); }; const onExternalLinkClicked = (event: OnExternalLinkClickedResult) => { // MapView will open the external link in the system's default browser if this callback is not set. - console.log("Situm > example > click on external link: " + event.url); + console.log("Situm> example> click on external link: " + event.url); }; const onFloorChanged = (event: any) => { - console.log("Situm > example > floor changed to: " + event.identifier); + console.log("Situm> example> floor changed to: " + event.identifier); }; const onFavoritePoisUpdated = (event: any) => { console.log( - "Situm > example > favorite pois updated: " + JSON.stringify(event.pois) + "Situm> example> favorite pois updated: " + JSON.stringify(event.pois) ); }; const onMapError = (error: any) => { - console.error("Situm > example > map error: " + error.message); + console.error("Situm> example> map error: " + error.message); }; // //////////////////////////////////////////////////////////////////////// diff --git a/plugin/src/wayfinding/components/MapView.tsx b/plugin/src/wayfinding/components/MapView.tsx index ddf4442..c5a8fb5 100644 --- a/plugin/src/wayfinding/components/MapView.tsx +++ b/plugin/src/wayfinding/components/MapView.tsx @@ -31,6 +31,7 @@ import { } from "../store"; import { useSelector } from "../store/utils"; import { + type OnInternalMapViewMessageCallback, type CartographySelectionOptions, type MapViewDirectionsOptions, type MapViewError, @@ -45,6 +46,7 @@ import { type OnPoiDeselectedResult, type OnPoiSelectedResult, type SearchFilter, + type InternalMapViewRef, } from "../types"; import { ErrorName } from "../types/constants"; import { sendMessageToViewer } from "../utils"; @@ -173,6 +175,8 @@ const MapView = React.forwardRef( const webViewRef = useRef(null); const [_onDirectionsRequestInterceptor, setInterceptor] = useState(); + const internalMessageCallbackRef = + useRef(); // Local states const [mapLoaded, setMapLoaded] = useState(false); @@ -353,7 +357,7 @@ const MapView = React.forwardRef( * onLoad={onLoad} /> */ - useImperativeHandle(ref, () => { + useImperativeHandle(ref, (): InternalMapViewRef => { return { followUser() { _followUser(true); @@ -409,6 +413,11 @@ const MapView = React.forwardRef( search(payload): void { _search(payload); }, + onInternalMapViewMessageCallback( + callback: OnInternalMapViewMessageCallback, + ): void { + internalMessageCallbackRef.current = callback; + }, }; }, [ stopNavigation, @@ -511,7 +520,7 @@ const MapView = React.forwardRef( ViewerMapper.initialConfiguration(style), ); - _disableInternalWebViewTTSEngine(); + _sendViewerConfigItems(); } }, [webViewRef, mapLoaded, style]); @@ -522,8 +531,15 @@ const MapView = React.forwardRef( }, [mapLoaded]); const handleRequestFromViewer = (event: WebViewMessageEvent) => { - const eventParsed = JSON.parse(event.nativeEvent.data); - switch (eventParsed.type) { + let eventParsed: any = {}; + try { + eventParsed = JSON.parse(event.nativeEvent.data); + } catch (err) { + console.warn("Invalid JSON from viewer:", err); + } + const type = eventParsed?.type ?? "message.unknown"; + const payload = eventParsed?.payload ?? {}; + switch (type) { case "app.map_is_ready": init(); setMapLoaded(true); @@ -531,53 +547,62 @@ const MapView = React.forwardRef( onLoad && onLoad(""); break; case "directions.requested": - calculateRoute(eventParsed.payload, _onDirectionsRequestInterceptor); + calculateRoute(payload, _onDirectionsRequestInterceptor); break; case "navigation.requested": - startNavigation(eventParsed.payload, _onDirectionsRequestInterceptor); + startNavigation(payload, _onDirectionsRequestInterceptor); break; case "navigation.stopped": stopNavigation(); break; case "cartography.poi_selected": - onPoiSelected(eventParsed?.payload); + onPoiSelected(payload); break; case "cartography.poi_deselected": - onPoiDeselected(eventParsed?.payload); + onPoiDeselected(payload); break; case "ui.favorite_pois_updated": { const favoritePoisIds = { - currentPoisIdentifiers: eventParsed.payload.favoritePois - ? [...eventParsed.payload.favoritePois] + currentPoisIdentifiers: payload.favoritePois + ? [...payload.favoritePois] : [], }; onFavoritePoisUpdated(favoritePoisIds); break; } case "cartography.floor_selected": - onFloorChanged(eventParsed?.payload); + onFloorChanged(payload); break; case "cartography.building_selected": if ( - !eventParsed.payload.identifier || - eventParsed.payload.identifier.toString() === buildingIdentifier + !payload.identifier || + payload.identifier.toString() === buildingIdentifier ) { return; } else { - setBuildingIdentifier(eventParsed.payload.identifier.toString()); + setBuildingIdentifier(payload.identifier.toString()); } break; case "viewer.navigation.started": case "viewer.navigation.updated": case "viewer.navigation.stopped": - SitumPlugin.updateNavigationState(eventParsed.payload); + SitumPlugin.updateNavigationState(payload); break; case "ui.speak_aloud_text": - SitumPlugin.speakAloudText(eventParsed.payload); + SitumPlugin.speakAloudText(payload); break; default: break; } + // Internal callback that will receive every MapView message. This callback + // has been introduced to enable communication between MapView and the new AR + // module, serving as a direct and extensible mode that avoids the + // intermediation of this plugin. + try { + internalMessageCallbackRef.current?.(type, payload); + } catch (error) { + console.error(`Error delegating ${type}:`, error); + } }; const _onShouldStartLoadWithRequest = (request: any) => { @@ -661,11 +686,15 @@ const MapView = React.forwardRef( return finalBuildingIdentifier; }, [configuration.buildingIdentifier]); - const _disableInternalWebViewTTSEngine = () => { + const _sendViewerConfigItems = async () => { + const deviceId = await SitumPlugin.getDeviceId(); sendMessageToViewer( webViewRef.current, ViewerMapper.setConfigItems([ + // Disable webview TTS in embed mode: { key: "internal.tts.engine", value: "mobile" }, + // Device ID: + { key: "internal.deviceId", value: deviceId || "" }, ]), ); }; diff --git a/plugin/src/wayfinding/types/index.ts b/plugin/src/wayfinding/types/index.ts index e4007b3..fdfa0c5 100644 --- a/plugin/src/wayfinding/types/index.ts +++ b/plugin/src/wayfinding/types/index.ts @@ -19,6 +19,10 @@ export interface MapViewRef { * @param poiId You can obtain the identifiers of your POIs by retrieving them with [SitumPlugin.fetchIndoorPOIsFromBuilding()](https://developers.situm.com/sdk_documentation/react-native/typedoc/classes/default.html#fetchIndoorPOIsFromBuilding). */ selectPoi: (poiId: number) => void; + /** + * Deselects any selected POI in the MapView's UI. + */ + deselectPoi: () => void; /** * Selects the given POI category and displays the list of POIs that belong to the given category. * Also, the POIs that do not belong to this category will be hidden in the map. @@ -102,6 +106,20 @@ export interface MapViewRef { navigateToCar: (params?: NavigateToCarPayload) => void; } +/** + * For internal use only. + * @internal + */ +export interface InternalMapViewRef extends MapViewRef { + /** + * Internal callback invoked with every MapView message. + * @internal + */ + onInternalMapViewMessageCallback: ( + delegate: OnInternalMapViewMessageCallback, + ) => void; +} + export interface WayfindingResult { status: string; message: string; @@ -145,6 +163,14 @@ export interface OnDirectionsRequestInterceptor { (directionRequest: DirectionsRequest): void; } +/** + * Represents an internal callback that will receive messages from the MapView. + * For internal use only. + */ +export interface OnInternalMapViewMessageCallback { + (type: string, payload: any): void; +} + export interface OnExternalLinkClickedResult { url: string; }