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
253 lines (219 loc) • 7.64 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;