@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
302 lines (250 loc) • 9.09 kB
text/typescript
import * as express from "express";
import * as _ from "lodash";
import * as cache from "memory-cache";
import {
BasePlugin,
PluginProperty,
AudienceFeedConnectorBaseInstanceContext,
AudienceFeed,
UserSegmentUpdateRequest,
ExternalSegmentCreationRequest,
ExternalSegmentConnectionRequest,
ExternalSegmentCreationPluginResponse,
ExternalSegmentConnectionPluginResponse,
UserSegmentUpdatePluginResponse,
AudienceFeedConnectorPluginResponse,
AudienceSegment
} from "../../../index";
export abstract class AudienceFeedConnectorBasePlugin extends BasePlugin {
instanceContext: Promise<AudienceFeedConnectorBaseInstanceContext>;
async fetchAudienceSegment(feedId: string): Promise<AudienceSegment> {
const response = await super.requestGatewayHelper(
"GET",
`${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}/audience_segment`
);
this.logger.debug(
`Fetched External Segment: FeedId: ${feedId} - ${JSON.stringify(response.data)}`
);
return response.data;
}
async fetchAudienceFeed(feedId: string): Promise<AudienceFeed> {
const response = await super.requestGatewayHelper(
"GET",
`${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}`
);
this.logger.debug(
`Fetched External Feed: ${feedId} - ${JSON.stringify(response.data)}`
);
return response.data;
}
async fetchAudienceFeedProperties(feedId: string): Promise<PluginProperty[]> {
const response = await super.requestGatewayHelper(
"GET",
`${this.outboundPlatformUrl}/v1/audience_segment_external_feeds/${feedId}/properties`
);
this.logger.debug(
`Fetched External Feed Properties: ${feedId} - ${JSON.stringify(
response.data
)}`
);
return response.data;
}
// Method to build an instance context
// To be overriden to get a cutom behavior
// This is a default provided implementation
protected async instanceContextBuilder(
feedId: string
): Promise<AudienceFeedConnectorBaseInstanceContext> {
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: AudienceFeedConnectorBaseInstanceContext = {
feed: audienceFeed,
feedProperties: audienceFeedProps
};
return context;
}
protected abstract onExternalSegmentCreation(
request: ExternalSegmentCreationRequest,
instanceContext: AudienceFeedConnectorBaseInstanceContext
): Promise<ExternalSegmentCreationPluginResponse>;
protected abstract onExternalSegmentConnection(
request: ExternalSegmentConnectionRequest,
instanceContext: AudienceFeedConnectorBaseInstanceContext
): Promise<ExternalSegmentConnectionPluginResponse>;
protected abstract onUserSegmentUpdate(
request: UserSegmentUpdateRequest,
instanceContext: AudienceFeedConnectorBaseInstanceContext
): Promise<UserSegmentUpdatePluginResponse>;
private emptyBodyFilter(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
if (!req.body || _.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();
}
}
private async getInstanceContext(
feedId: string
): Promise<AudienceFeedConnectorBaseInstanceContext> {
if (!this.pluginCache.get(feedId)) {
this.pluginCache.put(
feedId,
this.instanceContextBuilder(feedId),
this.INSTANCE_CONTEXT_CACHE_EXPIRATION
);
}
return this.pluginCache.get(feedId);
}
private initExternalSegmentCreation(): void {
this.app.post(
"/v1/external_segment_creation",
this.emptyBodyFilter,
async (req: express.Request, res: express.Response) => {
try {
this.logger.debug(
`POST /v1/external_segment_creation ${JSON.stringify(req.body)}`
);
const request = req.body as ExternalSegmentCreationRequest;
if (!this.onExternalSegmentCreation) {
throw new Error("No External Segment Creation listener registered!");
}
const instanceContext = await this.getInstanceContext(
request.feed_id
);
const response = await this.onExternalSegmentCreation(
request,
instanceContext
);
this.logger.debug(`Returning: ${JSON.stringify(response)}`);
const pluginResponse: AudienceFeedConnectorPluginResponse = {
status: response.status
};
if (response.message) {
pluginResponse.message = response.message;
}
const statusCode = (response.status === 'ok') ? 200 : 500;
return res.status(statusCode).send(JSON.stringify(pluginResponse));
} catch (error) {
this.logger.error(
`Something bad happened : ${error.message} - ${error.stack}`
);
return res.status(500).send({status: 'error', message: `${error.message}\n ${error.stack}`});
}
}
);
}
private initExternalSegmentConnection(): void {
this.app.post(
"/v1/external_segment_connection",
this.emptyBodyFilter,
async (req: express.Request, res: express.Response) => {
try {
this.logger.debug(
`POST /v1/external_segment_connection ${JSON.stringify(req.body)}`
);
const request = req.body as ExternalSegmentConnectionRequest;
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
);
this.logger.debug(`FeedId: ${request.feed_id} - Plugin impl returned: ${JSON.stringify(response)}`);
const pluginResponse: ExternalSegmentConnectionPluginResponse = {
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} - Returning: ${statusCode} - ${JSON.stringify(response)}`);
return res.status(statusCode).send(JSON.stringify(pluginResponse));
} catch (error) {
this.logger.error(
`Something bad happened : ${error.message} - ${error.stack}`
);
return res.status(500).send({status: 'error', message: `${error.message}\n ${error.stack}`});
}
}
);
}
private initUserSegmentUpdate(): void {
this.app.post(
"/v1/user_segment_update",
this.emptyBodyFilter,
async (req: express.Request, res: express.Response) => {
try {
this.logger.debug(
`POST /v1/user_segment_update ${JSON.stringify(req.body)}`
);
const request = req.body as UserSegmentUpdateRequest;
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
);
this.logger.debug(`Returning: ${JSON.stringify(response)}`);
const pluginResponse: AudienceFeedConnectorPluginResponse = {
status: response.status
};
if (response.nextMsgDelayInMs) {
res.set(
"x-mics-next-msg-delay",
response.nextMsgDelayInMs.toString()
);
}
if (response.message) {
pluginResponse.message = response.message;
}
return res.status(200).send(JSON.stringify(pluginResponse));
} catch (error) {
this.logger.error(
`Something bad happened : ${error.message} - ${error.stack}`
);
return res.status(500).send({status: 'error', message: `${error.message}\n ${error.stack}`});
}
}
);
}
constructor() {
super();
this.initExternalSegmentCreation();
this.initExternalSegmentConnection();
this.initUserSegmentUpdate();
}
}