# Messages
# Debugging
In case there are any issue with the communication between the app and the web app, it can be handy to enable some debug output from the web app. This can be achieved by injecting JavaScript code that overwrites the console output functions. Data from the console are then forwarded to the native app, through the domain message API.
const INJECT_JS = `
const INJECT_JS = `navigator.STORY_HUNT_APP_ID = ${STORY_HUNT_APP_ID}`;
// Forward debug from web app to native app
console = new Object();
console.log = function(log, opt) {
let msg = { type: 'Debug', content: { msg: log, opt: opt } };
window.ReactNativeWebView.postMessage(JSON.stringify(msg));
};
console.debug = console.log;
console.info = console.log;
console.warn = console.log;
console.error = console.log;
`;
# Authentication flow
# Login through web app
In some integrations where standard authentication providers (ie. Google, Facebook or StoryHunt auth) are used it can make sense to let the user login through the interface of the web app. In the reference implementation we do this by sending an LoginDomainMsg when the user presses one of the login options in the app. The native parent then processes this request and sends back a LoginDomainMsg with a idToken which is then verified by the web app.
The diagram below show the login message sequence, in the case where login is initiated from the web app interface:
Example Google login flow (initiated through web app)
# Login through native app
In other cases, where the user must log in through the native app interface, login sequence looks some what similar. However, in this case the native app initiates message flow as seen below:
Example custom authentication provider (DreamHack) login flow (initiated through native app)
# Example message implementation
It is possible to send domain messages to the web app via the postMessage function on the web view. The following example shows how a send message function could be implemented (the DomainMsg type is described later):
private async sendWebMsg(msg: DomainMsg) {
if(!this.webView) return console.warn("Could not send msg to webview");
// Send message to StoryHunt Web App
this.webView.postMessage(JSON.stringify(msg));
}
The following snippet shows and example of how to handle incoming domain messages from the StoryHunt web app, using the onMessage handler on the webview:
<WebView
injectedJavaScriptBeforeContentLoaded={INJECT_JS}
source={{ uri: STORY_HUNT_URL }}
onMessage={ (msg) => this.onWebViewMessage(msg.nativeEvent) }
//...
/>
/**
* Handle incoming messages from webview
*/
private onWebViewMessage = (ev: WebViewMessage) => {
if(!ev.data) return;
let msg: DomainMsg | null = null;
try {
msg = JSON.parse(ev.data);
}
catch {
// Invalid msg from client
console.info("Could not parse json msg from webview");
}
if(!msg || !msg.type) return;
this.parseAppMsg(msg);
}
private async parseAppMsg(msg:DomainMsg) {
switch(msg.type) {
case 'Debug':
if(msg.content.opt) console.log(msg.content.msg, msg.content.opt);
else console.log(msg.content.msg);
break;
case 'Initial':
console.log("<NativeApp> got 'Initial' msg: ", msg.content.msg);
// Send appId to web app
this.sendWebMsg( {
type: 'Initial',
content: {
msg: "Hello here is a init msg for web app",
appId: STORY_HUNT_APP_ID,
enableMsgDebug: false,
gpsDebugLevel: 'state'
}
});
break;
case 'Login':
let rsp = await LoginService.signIn(msg.content.provider);
this.sendWebMsg( rsp );
break;
default:
console.log("Msg from web app: ", msg);
break;
}
}
# Message Definitions
The following snippet shows TypeScript definitions and descriptions of the messages that are currently supported by the StoryHunt Web App.
import { AppConfig } from '../../autogen/motes-config';
import { MoteEventSerializable } from '../../autogen/mote-event-type';
import { MoteData } from '../../autogen/mote.data';
import { GeolocationPosition } from '../../autogen/geolocation';
import { SearchInputEvent } from '../../autogen/search-event';
import { LoginUser } from '../../autogen/login-user';
/**
* Cross domain message used to communicate between
* native app and Motes Web App or cross domain iframes
*/
export type DomainMsgType = 'Initial' | 'GeoLocation' | 'GeoLocationNative' | 'WatchGeoLocation' | 'PushNotification' | 'MotesConfig' | 'MoteEvent' | 'ProjectMotes' | 'Search' | 'SearchEvent' | 'InteractionEvent' | 'ClientConfig' | 'Alive' | 'Login' | 'LoginStatus' | 'Debug';
/**
* Cross domain message used to communicate between
* native app and Motes Web App or cross domain iframes
*/
export type DomainMsg = InitialDomainMsg | GeoLocationDomainMsg | GeoLocationNativeDomainMsg | WatchGeoLocationDomainMsg | PushNotificationDomainMsg | MotesConfigDomainMsg | MoteEventDomainMsg | ProjectMotesDomainMsg | SearchDomainMsg | SearchEventDomainMsg | InteractionDomainMsg | ClientConfigDomainMsg | AliveDomainMsg | LoginDomainMsg | LoginStatusDomainMsg | DebugDomainMsg;
/**
* Generic abstract domain msg for communication between child window
* (Motes Web App) and parent (Motes Native App or 3rd party webpage)
*/
interface AbstractDomainMsg {
type:DomainMsgType;
content?:any;
}
/**
* Initial message type used to init connection between child
* window and parent (mainly for the purpose of timing and domain
* initialization on load time). This can also be used to dynamically inject
* the StoryHuntAppId in cases where it is not possible to statically inject the id.
* @msg A custom info message string
* @appId StoryHuntAppId of parent app
* @enableMsgDebug Enable console.log debug output in webapp on domain message send/receive
*/
class InitialDomainMsg implements AbstractDomainMsg {
type:'Initial';
content: {
msg: string,
appId?: string,
enableMsgDebug?: boolean,
gpsDebugLevel?: 'state' | 'verbose',
}
}
/**
* Debug message to emulate user position from native app
* (coordinates will be injected and override normal GPS flow)
*/
class GeoLocationDomainMsg implements AbstractDomainMsg {
type:'GeoLocation';
content: {
lat: number,
long: number,
heading?: number,
accuracy?: number
};
}
/**
* Message to send user position from native app to web app
* (uses native position msg layout - will not be injected as emulation position,
* so must be used in conjunction with overwriting getCurrentPosition or watchCurrentPosition)
*/
class GeoLocationNativeDomainMsg implements AbstractDomainMsg {
type:'GeoLocationNative';
content: GeolocationPosition | GeolocationPositionError
}
/**
* Request native app to start watching for GeoLocation and continiously
* send GeoLocationNativeDomainMsg with position updates
*/
class WatchGeoLocationDomainMsg implements AbstractDomainMsg {
type:'WatchGeoLocation';
}
/**
* Show a pushnotification to user (of native app)
*/
class PushNotificationDomainMsg implements AbstractDomainMsg {
type:'PushNotification';
content:{
popupText: string
};
}
/**
* Runtime change Motes configuration
*/
class MotesConfigDomainMsg implements AbstractDomainMsg {
type:'MotesConfig';
content: AppConfig;
}
/**
* Mote events changing the state of the app
*/
class MoteEventDomainMsg implements AbstractDomainMsg {
type:'MoteEvent';
content: MoteEventSerializable;
}
/**
* Update msg containing active project user Motes data
*/
class ProjectMotesDomainMsg implements AbstractDomainMsg {
type:'ProjectMotes';
content: MoteData[];
}
/**
* Search for motes and places in the mote APP
*/
class SearchDomainMsg implements AbstractDomainMsg {
type:'Search';
content: string;
}
/**
* Search events from geo-search service in App
*/
class SearchEventDomainMsg implements AbstractDomainMsg {
type:'SearchEvent';
content: SearchInputEvent;
}
/**
* Interaction event (mouse and touch interaction) events from APP
* @class InteractionDomainMsg
*/
class InteractionDomainMsg implements AbstractDomainMsg {
type:'InteractionEvent';
content: string;
}
/**
* Interaction event (mouse and touch interaction) events from APP
* @class ClientConfigDomainMsg
*/
class ClientConfigDomainMsg implements AbstractDomainMsg {
type:'ClientConfig';
content: {clientId:string, configKey:string, config:any};
}
/**
* Alive msg to figure if app is still up and running
* @class AliveDomainMsg
*/
class AliveDomainMsg implements AbstractDomainMsg {
type:'Alive';
content: {'clientId':string, 'msg':any};
}
export type DomainLoginProvider = 'Google' | 'Facebook' | 'Apple' | 'DreamHack' | 'Unknown';
/**
* Login domain message requesting
*/
export class LoginDomainMsg implements AbstractDomainMsg {
type:'Login';
content: { provider: DomainLoginProvider, error?: string, idToken?: string};
}
/**
* Login domain message status reply
*/
export class LoginStatusDomainMsg implements AbstractDomainMsg {
type:'LoginStatus';
content: { provider: DomainLoginProvider, error?: string, idToken?: string, authorizationCode?: string, userInfo?: LoginUser, status: 'Success' | 'Error' };
}
/**
* Debug msg from app to be printed in debug console
* @class DebugDomainMsg
*/
class DebugDomainMsg implements AbstractDomainMsg {
type:'Debug';
content: {'msg': string, 'opt':any};
}