UNPKG

@ngageoint/mage.arcgis.service

Version:

A mage service plugin that synchronizes mage observations to a configured ArcGIS feature layer.

269 lines 16.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; const plugins_api_1 = require("@ngageoint/mage.service/lib/plugins.api"); const plugins_api_observations_1 = require("@ngageoint/mage.service/lib/plugins.api/plugins.api.observations"); const plugins_api_events_1 = require("@ngageoint/mage.service/lib/plugins.api/plugins.api.events"); const plugins_api_users_1 = require("@ngageoint/mage.service/lib/plugins.api/plugins.api.users"); const entities_permissions_1 = require("@ngageoint/mage.service/lib/entities/authorization/entities.permissions"); const ObservationProcessor_1 = require("./ObservationProcessor"); const arcgis_rest_request_1 = require("@esri/arcgis-rest-request"); const node_url_1 = require("node:url"); const express_1 = __importDefault(require("express")); const ArcGISService_1 = require("./ArcGISService"); const logPrefix = '[mage.arcgis]'; const logMethods = ['log', 'debug', 'info', 'warn', 'error']; const consoleOverrides = logMethods.reduce((overrides, fn) => { return Object.assign(Object.assign({}, overrides), { [fn]: { writable: false, value: (...args) => { globalThis.console[fn](new Date().toISOString(), '-', logPrefix, ...args); } } }); }, {}); const console = Object.create(globalThis.console, consoleOverrides); const pluginWebRoute = "plugins/@ngageoint/mage.arcgis.service"; const sanitizeFeatureService = (config, identityService) => __awaiter(void 0, void 0, void 0, function* () { let authenticated = false; try { yield identityService.signin(config); authenticated = true; } catch (error) { console.error('Error in sanitizeFeatureService :: ', error); } const { identityManager } = config, sanitized = __rest(config, ["identityManager"]); return Object.assign(Object.assign({}, sanitized), { authenticated }); }); /** * The MAGE ArcGIS Plugin finds new MAGE observations and if configured to send the observations * to an ArcGIS server, it will then transform the observation to an ArcGIS feature and * send them to the configured ArcGIS feature layer. */ const arcgisPluginHooks = { inject: { stateRepo: plugins_api_1.PluginStateRepositoryToken, eventRepo: plugins_api_events_1.MageEventRepositoryToken, obsRepoForEvent: plugins_api_observations_1.ObservationRepositoryToken, userRepo: plugins_api_users_1.UserRepositoryToken }, init: (services) => __awaiter(void 0, void 0, void 0, function* () { console.info('Intializing ArcGIS plugin...'); const { stateRepo, eventRepo, obsRepoForEvent, userRepo } = services; const identityService = (0, ArcGISService_1.createArcGISIdentityService)(stateRepo); const processor = new ObservationProcessor_1.ObservationProcessor(stateRepo, eventRepo, obsRepoForEvent, userRepo, identityService, console); yield processor.start(); return { webRoutes: { public: () => { const routes = express_1.default.Router().use(express_1.default.json()); routes.get('/oauth/signin', (req, res) => __awaiter(void 0, void 0, void 0, function* () { const url = req.query.featureServiceUrl; if (!node_url_1.URL.canParse(url)) { return res.status(404).send('invalid feature service url'); } const clientId = req.query.clientId; if (!clientId) { return res.status(404).send('clientId is required'); } const portalUrl = req.query.portalUrl; const config = yield processor.safeGetConfig(); arcgis_rest_request_1.ArcGISIdentityManager.authorize({ clientId, portal: portalUrl || (0, ArcGISService_1.getPortalUrl)(url), redirectUri: `${config.baseUrl}/${pluginWebRoute}/oauth/authenticate`, state: JSON.stringify({ url: url, clientId: clientId, portalUrl: portalUrl }) }, res); })); routes.get('/oauth/authenticate', (req, res) => __awaiter(void 0, void 0, void 0, function* () { const code = req.query.code; let state; try { const { url, clientId, portalUrl } = JSON.parse(req.query.state); state = { url, clientId, portalUrl }; } catch (err) { console.error('error parsing relay state', err); return res.sendStatus(500); } const config = yield processor.safeGetConfig(); const creds = { clientId: state.clientId, redirectUri: `${config.baseUrl}/${pluginWebRoute}/oauth/authenticate`, portal: state.portalUrl || (0, ArcGISService_1.getPortalUrl)(state.url) }; arcgis_rest_request_1.ArcGISIdentityManager.exchangeAuthorizationCode(creds, code).then((idManager) => __awaiter(void 0, void 0, void 0, function* () { let service = config.featureServices.find(service => service.url === state.url); if (!service) { service = { url: state.url, portalUrl: state.portalUrl, identityManager: idManager.serialize(), layers: [] }; } else { service.identityManager = idManager.serialize(); if (state.portalUrl) { service.portalUrl = state.portalUrl; } } config.featureServices.push(service); yield processor.putConfig(config); const sanitizedService = yield sanitizeFeatureService(service, identityService); res.send(` <html> <head> <script> window.opener.postMessage(${JSON.stringify(sanitizedService)}, '${req.protocol}://${req.headers.host}'); </script> </head> </html> `); })).catch((error) => res.status(400).json(error)); })); return routes; }, protected: (requestContext) => { const routes = express_1.default.Router() .use(express_1.default.json()) .use((req, res, next) => __awaiter(void 0, void 0, void 0, function* () { const context = requestContext(req); const user = context.requestingPrincipal(); if (!user.role.permissions.find(x => x === entities_permissions_1.SettingPermission.UPDATE_SETTINGS)) { return res.status(403).json({ message: 'unauthorized' }); } next(); })); routes.route('/config') .get((req, res) => __awaiter(void 0, void 0, void 0, function* () { console.info('Getting ArcGIS plugin config...'); const config = yield processor.safeGetConfig(); const { featureServices } = config, remaining = __rest(config, ["featureServices"]); const sanitizeFeatureServices = yield Promise.all(featureServices.map((service) => __awaiter(void 0, void 0, void 0, function* () { return yield sanitizeFeatureService(service, identityService); }))); res.json(Object.assign(Object.assign({}, remaining), { featureServices: sanitizeFeatureServices })); })) .put((req, res) => __awaiter(void 0, void 0, void 0, function* () { console.info('Applying ArcGIS plugin config...'); const config = yield stateRepo.get(); const _a = req.body, { featureServices: updatedServices } = _a, updateConfig = __rest(_a, ["featureServices"]); // Convert event names to event IDs // Fetch all events and create a mapping of event names to event IDs const allEvents = yield eventRepo.findAll(); const eventNameToIdMap = new Map(); allEvents.forEach(event => { eventNameToIdMap.set(event.name, event.id); }); // Process the incoming feature services with eventIds instead of event names const featureServices = updatedServices.map((updateService) => { const existingService = config.featureServices.find((featureService) => featureService.url === updateService.url); // Process layers const layers = updateService.layers.map((layer) => { // Extract event names from the incoming layer data const eventNames = layer.events || []; // Convert event names to event IDs using the mapping const eventIds = eventNames .map(eventName => eventNameToIdMap.get(eventName)) .filter((id) => id !== undefined); // Construct the FeatureLayerConfig with eventIds const featureLayerConfig = Object.assign(Object.assign({}, layer), { eventIds: eventIds }); return featureLayerConfig; }); return { url: updateService.url, portalUrl: updateService.portalUrl, layers: layers, // Map existing identityManager, client does not send this identityManager: (existingService === null || existingService === void 0 ? void 0 : existingService.identityManager) || '', }; }); yield stateRepo.patch(Object.assign(Object.assign({}, updateConfig), { featureServices })); // Sync configuration with feature servers by restarting observation processor processor.stop(); yield processor.start(); res.status(200).json({ success: true }); })); routes.post('/featureService/validate', (req, res) => __awaiter(void 0, void 0, void 0, function* () { const config = yield processor.safeGetConfig(); const { url, portalUrl, token, username, password } = req.body; if (!node_url_1.URL.canParse(url)) { return res.send('Invalid feature service url').status(400); } let service; let identityManager; if (token) { identityManager = yield arcgis_rest_request_1.ArcGISIdentityManager.fromToken({ token }); service = { url, portalUrl, layers: [], identityManager: identityManager.serialize() }; } else if (username && password) { identityManager = yield arcgis_rest_request_1.ArcGISIdentityManager.signIn({ username, password, portal: portalUrl || (0, ArcGISService_1.getPortalUrl)(url) }); service = { url, portalUrl, layers: [], identityManager: identityManager.serialize() }; } else { return res.sendStatus(400); } try { const existingService = config.featureServices.find(service => service.url === url); if (!existingService) { config.featureServices.push(service); } yield processor.patchConfig(config); const sanitized = yield sanitizeFeatureService(service, identityService); return res.send(sanitized); } catch (err) { return res.send('Invalid credentials provided to communicate with feature service' + err).status(400); } })); routes.get('/featureService/layers', (req, res) => __awaiter(void 0, void 0, void 0, function* () { const url = req.query.featureServiceUrl; const config = yield processor.safeGetConfig(); const featureService = config.featureServices.find(featureService => featureService.url === url); if (!featureService) { return res.status(400); } try { const identityManager = yield identityService.signin(featureService); const response = yield (0, arcgis_rest_request_1.request)(url, { authentication: identityManager }); res.send(response.layers); } catch (err) { console.error(err); res.status(500).json({ message: 'Could not get ArcGIS layer info', error: err }); } })); return routes; } } }; }) }; module.exports = arcgisPluginHooks; //# sourceMappingURL=index.js.map