UNPKG

@mediarithmics/plugins-nodejs-sdk

Version:

This is the mediarithmics nodejs to help plugin developers bootstrapping their plugin without having to deal with most of the plugin boilerplate

424 lines 20 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AudienceFeedConnectorBasePlugin = exports.BatchedAudienceFeedConnectorBasePlugin = void 0; const UserAgentIdentifierRealmSelectionInterface_1 = require("./../../api/core/webdomain/UserAgentIdentifierRealmSelectionInterface"); const lodash_1 = __importDefault(require("lodash")); const BatchUpdateHandler_1 = require("../../api/core/batchupdate/BatchUpdateHandler"); const AudienceFeedConnectorPluginResponseInterface_1 = require("../../api/plugin/audiencefeedconnector/AudienceFeedConnectorPluginResponseInterface"); const AudienceFeedConnectorRequestInterface_1 = require("../../api/plugin/audiencefeedconnector/AudienceFeedConnectorRequestInterface"); const common_1 = require("../common"); class GenericAudienceFeedConnectorBasePlugin extends common_1.BasePlugin { constructor(enableThrottling = false) { super(enableThrottling); this.initExternalSegmentCreation(); this.initExternalSegmentConnection(); this.initUserSegmentUpdate(); this.initTroubleshoot(); this.initAuthenticationStatusQuery(); this.initAuthentication(); this.initLogoutQuery(); this.initDynamicPropertyValuesQuery(); } async fetchAudienceSegment(feedId) { const response = await super.requestGatewayHelper('GET', `${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}/audience_segment`); this.logger.debug(`Fetched External Segment: FeedId: ${feedId}`, response); return response.data; } async fetchUserAgentIdentifierRealms(datamartId) { const response = await super.requestGatewayHelper('GET', `${this.outboundPlatformUrl}/v1/datamarts/${datamartId}/user_agent_identifier_realm_selections`); this.logger.debug(`Fetched user agent identifier realms for the datamart with id: ${datamartId}`); return response.data; } async checkUserAgentIdentifierRealm(datamartId, realmFilter) { const realms = await this.fetchUserAgentIdentifierRealms(datamartId); const hasRealm = realms.some((realm) => { if ((0, UserAgentIdentifierRealmSelectionInterface_1.isWebDomainRealmFilter)(realmFilter)) { return realm.realm_type === realmFilter.realmType && realm.web_domain.sld_name === realmFilter.sld_name; } else return realm.realm_type === realmFilter.realmType; }); if (!hasRealm) { throw new AudienceFeedConnectorPluginResponseInterface_1.MissingRealmError(datamartId, realmFilter); } } async fetchAudienceFeed(feedId) { const response = await super.requestGatewayHelper('GET', `${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}`); this.logger.debug(`Fetched External Feed: ${feedId}`, { response }); return response.data; } // Method to build an instance context // To be overriden to get a cutom behavior async fetchAudienceFeedProperties(feedId) { const response = await super.requestGatewayHelper('GET', `${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}/properties`); this.logger.debug(`Fetched External Feed Properties: ${feedId}`, { response }); return response.data; } async createAudienceFeedProperties(feedId, property) { const response = await super.requestGatewayHelper('POST', `${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}/properties`, property); this.logger.debug(`Created External Feed Properties: ${feedId}`, { response }); return response.data; } async updateAudienceFeedProperties(feedId, property) { const response = await super.requestGatewayHelper('PUT', `${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}/properties/technical_name=${property.technical_name}`, property); this.logger.debug(`Updated External Feed Properties: ${feedId}`, { response }); return response.data; } // This is a default provided implementation async instanceContextBuilder(feedId) { const audienceFeedP = this.fetchAudienceFeed(feedId); const audienceFeedPropsP = this.fetchAudienceFeedProperties(feedId); const results = await Promise.all([audienceFeedP, audienceFeedPropsP]); const audienceFeed = results[0]; const audienceFeedProps = results[1]; const context = { feed: audienceFeed, feedProperties: new common_1.PropertiesWrapper(audienceFeedProps), }; return context; } onTroubleshoot(request, instanceContext) { return Promise.resolve({ status: 'not_implemented' }); } onAuthenticationStatusQuery(request) { return Promise.resolve({ status: 'not_implemented' }); } onAuthentication(request) { return Promise.resolve({ status: 'not_implemented' }); } onLogout(request) { return Promise.resolve({ status: 'not_implemented' }); } onDynamicPropertyValuesQuery(request) { return Promise.resolve({ status: 'not_implemented' }); } async getInstanceContext(feedId, forceRefresh) { if (forceRefresh || !this.pluginCache.get(feedId)) { void this.pluginCache.put(feedId, this.instanceContextBuilder(feedId).catch((error) => { this.logger.error(`Error while caching instance context`, error); this.pluginCache.del(feedId); throw error; }), this.getInstanceContextCacheExpiration()); } return this.pluginCache.get(feedId); } emptyBodyFilter(req, res, next) { if (!req.body || lodash_1.default.isEmpty(req.body)) { const msg = { error: 'Missing request body', }; this.logger.error(`POST /v1/${req.url} : %s`, JSON.stringify(msg)); res.status(500).json(msg); } else { next(); } } initExternalSegmentCreation() { this.app.post('/v1/external_segment_creation', this.emptyBodyFilter, async (req, res) => { try { this.logger.debug('POST /v1/external_segment_creation', { request: req.body }); if (!this.httpIsReady()) { throw new Error('Plugin not initialized'); } const request = req.body; if (!this.onExternalSegmentCreation) { throw new Error('No External Segment Creation listener registered!'); } const instanceContext = await this.getInstanceContext(request.feed_id, true); const response = await this.onExternalSegmentCreation(request, instanceContext); const pluginResponse = { status: response.status, visibility: response.visibility || 'PUBLIC', }; if (response.message) { pluginResponse.message = response.message; } const statusCode = response.status === 'ok' ? 200 : 500; this.logger.debug(`FeedId: ${request.feed_id} - External segment creation returning: ${statusCode}`, { response, }); return res.status(statusCode).send(JSON.stringify(pluginResponse)); } catch (error) { this.logger.error('Something bad happened on creation', error); const pluginResponse = { status: 'error', message: `${error.message}`, visibility: error.visibility === 'PUBLIC' ? 'PUBLIC' : 'PRIVATE', }; return res.status(500).send(pluginResponse); } }); } initExternalSegmentConnection() { this.app.post('/v1/external_segment_connection', this.emptyBodyFilter, async (req, res) => { try { this.logger.debug('POST /v1/external_segment_connection', { request: req.body }); if (!this.httpIsReady()) { throw new Error('Plugin not initialized'); } const request = req.body; if (!this.onExternalSegmentConnection) { throw new Error('No External Segment Connection listener registered!'); } const instanceContext = await this.getInstanceContext(request.feed_id); const response = await this.onExternalSegmentConnection(request, instanceContext); const pluginResponse = { status: response.status, }; if (response.message) { pluginResponse.message = response.message; } let statusCode; switch (response.status) { case 'external_segment_not_ready_yet': statusCode = 502; break; case 'ok': statusCode = 200; break; case 'error': statusCode = 500; break; default: statusCode = 500; } this.logger.debug(`FeedId: ${request.feed_id} - External segment connection returning: ${statusCode}`, { response, }); return res.status(statusCode).send(JSON.stringify(pluginResponse)); } catch (error) { this.logger.error('Something bad happened on connection', error); return res.status(500).send({ status: 'error', message: `${error.message}` }); } }); } initUserSegmentUpdate() { this.app.post('/v1/user_segment_update', this.emptyBodyFilter, async (req, res) => { try { this.logger.debug('POST /v1/user_segment_update', { request: req.body }); const request = req.body; if (!this.onUserSegmentUpdate) { throw new Error('No User Segment Update listener registered!'); } const instanceContext = await this.getInstanceContext(request.feed_id); const response = await this.onUserSegmentUpdate(request, instanceContext); if (response.next_msg_delay_in_ms) { res.set('x-mics-next-msg-delay', response.next_msg_delay_in_ms.toString()); } let statusCode; switch (response.status) { case 'ok': statusCode = 200; break; case 'error': statusCode = 500; break; case 'retry': statusCode = 429; break; case 'no_eligible_identifier': statusCode = 400; break; default: statusCode = 500; } this.logger.debug(`FeedId: ${request.feed_id} - External segment update returning: ${statusCode}`, { response, }); return res.status(statusCode).send(JSON.stringify(response)); } catch (error) { this.logger.error('Something bad happened on update', error); return res.status(500).send({ status: 'error', message: `${error.message}` }); } }); } initTroubleshoot() { this.app.post('/v1/troubleshoot', this.emptyBodyFilter, async (req, res) => { try { this.logger.debug('POST /v1/troubleshoot', { request: req.body }); const request = req.body; if (!AudienceFeedConnectorRequestInterface_1.ExternalSegmentTroubleshootActions.includes(request.action)) { const response = { status: 'not_implemented', message: `Action ${request.action} not supported`, }; return res.status(400).send(JSON.stringify(response)); } const instanceContext = await this.getInstanceContext(request.feed_id); const response = await this.onTroubleshoot(request, instanceContext); let statusCode; switch (response.status) { case 'ok': statusCode = 200; break; case 'error': statusCode = 500; break; case 'not_implemented': statusCode = 400; break; default: statusCode = 500; } this.logger.debug(`FeedId: ${request.feed_id} - Troubleshoot returning: ${statusCode}`, { response }); return res.status(statusCode).send(JSON.stringify(response)); } catch (error) { this.logger.error('Something bad happened on troubleshoot', error); return res.status(500).send({ status: 'error', message: `${error.message}` }); } }); } initAuthenticationStatusQuery() { this.app.post('/v1/authentication_status_queries', this.emptyBodyFilter, async (req, res) => { try { const request = req.body; const response = await this.onAuthenticationStatusQuery(request); let statusCode; switch (response.status) { case 'authenticated': case 'not_authenticated': statusCode = 200; break; case 'error': statusCode = 500; break; case 'not_implemented': statusCode = 400; break; default: statusCode = 500; } this.logger.debug(`Request: ${JSON.stringify(request)} - Authentication status query returning: ${statusCode}`, { response, }); return res.status(statusCode).send(JSON.stringify(response)); } catch (error) { this.logger.error('Something bad happened on authentication status query', error); return res.status(500).send({ status: 'error', message: `${error.message}` }); } }); } initAuthentication() { this.app.post('/v1/authentication', this.emptyBodyFilter, async (req, res) => { try { const request = req.body; const response = await this.onAuthentication(request); let statusCode; switch (response.status) { case 'ok': statusCode = 200; break; case 'error': statusCode = 500; break; case 'not_implemented': statusCode = 400; break; default: statusCode = 500; } this.logger.debug(`Request: ${JSON.stringify(req.body)} - Authentication returning: ${statusCode}`, { response, }); return res.status(statusCode).send(JSON.stringify(response)); } catch (error) { this.logger.error('Something bad happened on authentication', error); return res.status(500).send({ status: 'error', message: `${error.message}` }); } }); } initLogoutQuery() { this.app.post('/v1/logout', this.emptyBodyFilter, async (req, res) => { try { const request = req.body; const response = await this.onLogout(request); let statusCode; switch (response.status) { case 'ok': statusCode = 200; break; case 'error': statusCode = 500; break; case 'not_implemented': statusCode = 400; break; default: statusCode = 500; } this.logger.debug(`Request: ${JSON.stringify(request)} - Logout query returning: ${statusCode}`, { response, }); return res.status(statusCode).send(JSON.stringify(response)); } catch (error) { this.logger.error('Something bad happened on logout query', error); return res.status(500).send({ status: 'error', message: `${error.message}` }); } }); } initDynamicPropertyValuesQuery() { this.app.post('/v1/dynamic_property_values_queries', this.emptyBodyFilter, async (req, res) => { try { const request = req.body; const response = await this.onDynamicPropertyValuesQuery(request); let statusCode; switch (response.status) { case 'ok': statusCode = 200; break; case 'error': statusCode = 500; break; case 'not_implemented': statusCode = 400; break; default: statusCode = 500; } this.logger.debug(`Request: ${JSON.stringify(request)} - Dynamic property values query returning: ${statusCode}`, { response, }); return res.status(statusCode).send(JSON.stringify(response)); } catch (error) { this.logger.error('Something bad happened on dynamic property values query', error); return res.status(500).send({ status: 'error', message: `${error.message}` }); } }); } } class BatchedAudienceFeedConnectorBasePlugin extends GenericAudienceFeedConnectorBasePlugin { constructor(enableThrottling = false) { super(enableThrottling); const batchUpdateHandler = new BatchUpdateHandler_1.BatchUpdateHandler(this.app, this.emptyBodyFilter, this.logger); batchUpdateHandler.registerRoute(async (request) => { const instanceContext = await this.getInstanceContext(request.context.feed_id); return this.onBatchUpdate(request, instanceContext); }); } } exports.BatchedAudienceFeedConnectorBasePlugin = BatchedAudienceFeedConnectorBasePlugin; class AudienceFeedConnectorBasePlugin extends GenericAudienceFeedConnectorBasePlugin { constructor(enableThrottling = false) { super(enableThrottling); this.initBatchUpdate(); } initBatchUpdate() { this.app.post('/v1/batch_update', this.emptyBodyFilter, async (req, res) => { res.status(500).send({ status: 'error', message: "Plugin doesn't support batch update" }); }); } } exports.AudienceFeedConnectorBasePlugin = AudienceFeedConnectorBasePlugin; //# sourceMappingURL=AudienceFeedConnectorBasePlugin.js.map