@ngageoint/mage.arcgis.service
Version:
A mage service plugin that synchronizes mage observations to a configured ArcGIS feature layer.
269 lines • 16.2 kB
JavaScript
;
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