atriusmaps-node-sdk
Version:
This project provides an API to Atrius Personal Wayfinder maps within a Node environment. See the README.md for more information
233 lines (199 loc) • 7.52 kB
JavaScript
'use strict';
var throttleDebounce = require('throttle-debounce');
var geom = require('../../../src/utils/geom.js');
var observable = require('../../../src/utils/observable.js');
var sdkHeadless = require('./sdkHeadless.js');
function _interopNamespaceDefaultOnly (e) { return Object.freeze({ __proto__: null, default: e }); }
let removePreviousListener = null;
// browser based communication
// listens for messages passed through
// postMessage and converts them to clientAPI/execute calls..
// responds by sending messages back through postMessage
// returns a sendEvent function used for sending events through postMessage as well
function getBrowserCom (app) {
const clean = ob => JSON.parse(JSON.stringify(ob));
const sendResponse = (payload, clientMsgId) => {
const ob = {
payload,
type: 'LL-server'
};
if (clientMsgId)
ob.clientMsgId = clientMsgId;
try {
window.postMessage(ob, '*');
} catch (e) { window.postMessage(clean(ob), '*'); } // failure to send could be due to non-marshalable object - this helps
};
const sendError = (payload, clientMsgId) => {
const ob = {
error: true,
payload,
type: 'LL-server'
};
if (clientMsgId)
ob.clientMsgId = clientMsgId;
window.postMessage(ob, '*');
};
const sendEvent = (event, payload) => {
const ob = {
event,
payload,
type: 'LL-server'
};
window.postMessage(ob, '*');
};
function msgHandler (e) {
const d = e.data;
if (d && d.type === 'LL-client') { // confirm message is from our "client"
app.bus.get('clientAPI/execute', d.payload)
.then(resOb => sendResponse(resOb, d.msgId))
.catch(er => {
if (app.config.debug)
console.error(er);
sendError(er.message, d.msgId);
});
}
}
// Listen for HTML5 postMessage events - these come from SDKClient apps
if (removePreviousListener)
removePreviousListener();
window.addEventListener('message', msgHandler);
removePreviousListener = () => window.removeEventListener('message', msgHandler);
return sendEvent
}
// For node, command sending and response sending is handled via
// nodeEntry - no postMessage required.
// sendEvent attaches a hook into app to receive them...
function getNodeCom (app) {
const eventListener = observable();
app.eventListener = eventListener;
const sendEvent = (event, payload) => eventListener.fire(event, payload);
return sendEvent
}
function registerCustomTypes (app) {
app.bus.send('clientAPI/registerCustomType', {
name: 'latLngOrdLocation',
spec: {
type: 'object',
props: [
{ name: 'lat', type: 'float' },
{ name: 'lng', type: 'float' },
{ name: 'ord', type: 'integer' }
]
}
});
app.bus.send('clientAPI/registerCustomType', {
name: 'latLngFloorLocation',
spec: {
type: 'object',
props: [
{ name: 'lat', type: 'float' },
{ name: 'lng', type: 'float' },
{ name: 'floorId', type: 'string' }
]
}
});
app.bus.send('clientAPI/registerCustomType', {
name: 'poiIdLocation',
spec: {
type: 'object',
props: [
{ name: 'poiId', type: 'integer', min: 0 }
]
}
});
app.bus.send('clientAPI/registerCustomType', {
name: 'location',
spec: {
type: 'multi',
types: [
{ type: 'poiIdLocation' },
{ type: 'latLngOrdLocation' },
{ type: 'latLngFloorLocation' }
]
}
});
app.bus.send('clientAPI/registerCustomType', {
name: 'viewSettings',
spec: {
type: 'object',
props: [
{ name: 'zoom', type: 'float', optional: true },
{ name: 'pitch', type: 'float', optional: true },
{ name: 'bearing', type: 'float', optional: true }
]
}
});
}
function registerEvents (app, sendEvent) {
app.bus.monitor('map/userMoveStart', async ({ pitch, zoom, bearing }) => {
const { lat, lng, floorId, ordinal, structureId } = await app.bus.get('map/getMapCenter');
sendEvent('userMoveStart', { lat, lng, floorId, ord: ordinal, structureId, pitch, zoom, bearing });
});
const userMoving = async ({ pitch, zoom, bearing }) => {
const { lat, lng, floorId, ordinal, structureId } = await app.bus.get('map/getMapCenter');
sendEvent('userMoving', { lat, lng, floorId, ord: ordinal, structureId, pitch, zoom, bearing });
};
app.bus.monitor('map/userMoving', throttleDebounce.throttle(500, userMoving));
app.bus.monitor('map/moveEnd', async ({ pitch, zoom, bearing }) => {
const { lat, lng, floorId, ordinal, structureId } = await app.bus.get('map/getMapCenter');
sendEvent('moveEnd', { lat, lng, floorId, ord: ordinal, structureId, pitch, zoom, bearing });
});
app.bus.monitor('map/floorChanged', ({ structure, floor }) =>
sendEvent('levelChange', {
floorId: floor ? floor.id : null,
floorName: floor ? floor.name : null,
ord: floor ? floor.ordinal : null,
structureId: structure ? structure.id : null,
structureName: structure ? structure.name : null
}));
app.bus.monitor('map/poiClicked', ({ poi }) =>
sendEvent('poiSelected', poi));
app.bus.monitor('poiDetails/showPoi', ({ poi }) =>
sendEvent('poiShown', poi));
app.bus.monitor('map/click', async ({ lat, lng, ord }) => {
const structures = await app.bus.get('venueData/getStructures');
const mapviewBBox = await app.bus.get('map/getViewBBox');
const { building: structure, floor } = geom.getStructureAndFloorAtPoint(structures, lat, lng, ord, mapviewBBox, true);
sendEvent('mapClicked', { lat, lng, ord, building: structure, floor });
});
}
async function create (app, config) {
const sendEvent = app.env.isBrowser
? getBrowserCom(app)
: getNodeCom(app);
const init = async () => {
// Register the Custom Types
registerCustomTypes(app);
// Register our commands
sdkHeadless.headlessCommands.forEach(cdef => app.bus.send('clientAPI/registerCommand', cdef));
sdkHeadless.handleHeadless(app);
if (!config.headless) {
await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefaultOnly(require('../../../_virtual/_empty_module_placeholder.js')); })
.then(sdkVisual => {
sdkVisual.visualCommands.forEach(cdef => app.bus.send('clientAPI/registerCommand', cdef));
sdkVisual.handleVisual(app, sendEvent);
});
}
const weReady = async () => {
await app.bus.send('system/readywhenyouare'); // give other modules a chance to hold us up...
app.bus.get('clientAPI/execute', { command: 'getCommandJSON' })
.then(commandJSON => sendEvent('ready', { commandJSON }));
if (!config.headless && app.config.uiHide && app.config.uiHide.sidebar && app.env.isDesktop())
app.bus.send('map/changePadding', { padding: { left: 55, right: 55, top: 72, bottom: 22 } });
};
if (config.headless) // in headless case, we are ready once data is ready
Promise.all([
new Promise(resolve => app.bus.monitor('venueData/navGraphLoaded', resolve)),
new Promise(resolve => app.bus.monitor('venueData/poiDataLoaded', resolve))
]).then(weReady);
else
app.bus.on('map/mapReadyToShow', weReady);
app.bus.on('sdkServer/sendEvent', ({ eventName, ...props }) => sendEvent(eventName, props));
};
// Register Events
registerEvents(app, sendEvent);
return {
init
}
}
exports.create = create;