@fanoutio/grip
Version:
GRIP Interface Library
120 lines (119 loc) • 5.73 kB
JavaScript
import { Item, HttpResponseFormat, HttpStreamFormat, } from '../data/index.js';
import { PublisherClient } from './PublisherClient.js';
import { parseGripUri, validateSig } from '../utilities/index.js';
// The Publisher class allows consumers to easily publish HTTP response
// and HTTP stream format messages to GRIP proxies. Publisher can be configured
// using IGripConfig objects.
export class Publisher {
clients = [];
prefix;
constructor(config, publisherOptions) {
this.applyConfigs(config ?? [], publisherOptions);
this.prefix = publisherOptions?.prefix;
}
// Apply the specified GRIP configurations to this PublisherBase instance.
applyConfigs(config, publisherClientOptions) {
const configsAsArray = Array.isArray(config) ? config : [config];
for (const configEntry of configsAsArray) {
this.applyConfig(configEntry, publisherClientOptions);
}
}
// Apply the specified GRIP configuration to this PublisherBase instance.
// The parameter corresponds to a single PublisherClient instance. Each object
// will be parsed and a PublisherClient will be created either using just
// a URI or a URI and authentication information (Basic, JWT, or Bearer Token).
applyConfig(config, publisherClientOptions) {
const gripConfig = typeof config === 'string' ? parseGripUri(config) : config;
const client = new PublisherClient(gripConfig, {
fetch: publisherClientOptions?.fetch,
});
this.addClient(client);
}
// Add the specified PublisherClient instance.
addClient(client) {
this.clients.push(client);
}
// The publish method for publishing the specified item to the specified
// channel on the configured endpoint.
// This function returns a promise which is resolved when the publish is complete,
// or rejected with an exception describing the failure if the publish fails.
async publish(channel, item) {
await Promise.all(this.clients.map((client) => client.publish((this.prefix ?? '') + channel, item)));
}
// A utility method for publishing an item to the specified channel on the configured endpoint
// by building it from a single Format object or array of Format objects.
// This function returns a promise which is resolved when the publish is complete,
// or rejected with an exception describing the failure if the publish fails.
async publishFormats(channel, formats, id, prevId) {
await this.publish(channel, new Item(formats, id, prevId));
}
// Publish an HTTP response format message to all of the configured
// PubControlClients with a specified channel, message, and optional ID, and
// previous ID. The 'data' parameter may be provided as either an HttpResponseFormat
// instance or a string (in which case an HttpResponseFormat instance will
// be created and have the 'body' field set to the specified string).
// This function returns a promise which is resolved when the publish is complete,
// or rejected with an exception describing the failure if the publish fails.
async publishHttpResponse(channel, data, id, prevId) {
const httpResponse = data instanceof HttpResponseFormat ? data : new HttpResponseFormat({ body: data });
return this.publishFormats(channel, httpResponse, id, prevId);
}
// Publish an HTTP stream format message to all of the configured
// PubControlClients with a specified channel, message, and optional ID, and
// previous ID. The 'data' parameter may be provided as either an HttpStreamFormat
// instance or a string (in which case an HttpStreamFormat instance will
// be created and have the 'content' field set to the specified string).
// This function returns a promise which is resolved when the publish is complete,
// or rejected with an exception describing the failure if the publish fails.
async publishHttpStream(channel, data, id, prevId) {
const httpStream = data instanceof HttpStreamFormat ? data : new HttpStreamFormat(data);
return this.publishFormats(channel, httpStream, id, prevId);
}
async validateGripSig(gripSigHeaderValue) {
let isProxied = false;
let needsSigned = false;
let isSigned = false;
if (gripSigHeaderValue == null || this.clients.length === 0) {
return {
isProxied,
needsSigned,
isSigned,
};
}
let signatureValidated = false;
// The value needs to be appropriately signed if all the publisher clients
// have a "verify key".
needsSigned = true;
for (const client of this.clients) {
const verifyKey = client.getVerifyKey?.();
if (verifyKey == null) {
needsSigned = false;
break;
}
// We only need to validate the signature for one client
if (!signatureValidated) {
const verifyIss = client.getVerifyIss?.();
if (verifyIss == null) {
signatureValidated = await validateSig(gripSigHeaderValue, verifyKey);
}
else {
signatureValidated = await validateSig(gripSigHeaderValue, verifyKey, verifyIss);
}
}
}
if (needsSigned) {
if (signatureValidated) {
isProxied = true;
isSigned = true;
}
}
else {
isProxied = true;
}
return {
isProxied,
needsSigned,
isSigned,
};
}
}