@fanoutio/grip
Version:
GRIP Interface Library
113 lines (112 loc) • 4.19 kB
JavaScript
import * as Auth from '../auth/index.js';
import { isSymmetricSecret, loadKey } from '../utilities/index.js';
import { PublishException } from './PublishException.js';
const textEncoder = new TextEncoder();
// The PublisherClient class allows consumers to publish to an endpoint of
// their choice. The consumer wraps a Format class instance in an Item class
// instance and passes that to the publish method.
export class PublisherClient {
publishUri;
_auth;
_verifyComponents;
options;
constructor(gripConfig, options) {
let { control_uri: controlUri, control_iss: iss, user, pass, key: keyValue, verify_key: verifyKeyValue, verify_iss: verifyIss } = gripConfig;
const url = new URL(controlUri);
if (!url.pathname.endsWith('/')) {
// To avoid breaking previous implementation, if the URL does
// not end in a slash then we add one.
// e.g. if URI is 'https://www.example.com/foo' then the
// publishing URI is 'https://www.example.com/foo/publish'
url.pathname += '/';
}
url.pathname += 'publish/';
this.publishUri = String(url);
if (iss != null) {
const key = keyValue != null ? loadKey(keyValue) : new Uint8Array();
this._auth = new Auth.Jwt({ iss }, key);
// For backwards-compatibility reasons, if JWT authorization is used with a
// symmetric secret and if `verify_key` is not provided, then `key` will also
// be used as the `verify_key` value as well.
if (isSymmetricSecret(key) && verifyKeyValue == null) {
verifyKeyValue = keyValue;
}
}
else if (typeof keyValue === 'string') {
this._auth = new Auth.Bearer(keyValue);
}
else if (user != null && pass != null) {
this._auth = new Auth.Basic(user, pass);
}
if (verifyIss != null || verifyKeyValue != null) {
const verifyKey = verifyKeyValue != null ? loadKey(verifyKeyValue) : undefined;
this._verifyComponents = {
verifyIss,
verifyKey,
};
}
this.options = options ?? {};
}
getAuth() {
return this._auth;
}
getVerifyIss() {
return this._verifyComponents?.verifyIss;
}
getVerifyKey() {
return this._verifyComponents?.verifyKey;
}
// The publish method for publishing the specified item to the specified
// channel on the configured endpoint.
async publish(channel, item) {
const i = item.export();
i.channel = channel;
const authHeader = this._auth != null ? await this._auth.buildHeader() : null;
const items = [i];
// Prepare Request Body
const content = JSON.stringify({ items });
// Build HTTP headers
const headers = {
'Content-Type': 'application/json',
'Content-Length': String(textEncoder.encode(content).length),
};
if (authHeader != null) {
headers['Authorization'] = authHeader;
}
const init = {
method: 'POST',
headers,
body: content,
};
let res;
try {
res = await (this.options.fetch ?? fetch)(this.publishUri, init);
}
catch (err) {
throw new PublishException(err instanceof Error ? err.message : String(err), { statusCode: -1 });
}
let mode;
let data;
try {
mode = 'end';
data = await res.text();
}
catch (err) {
mode = 'close';
data = err;
}
const context = {
statusCode: res.status,
headers: res.headers,
httpBody: data,
};
if (mode === 'end') {
if (context.statusCode < 200 || context.statusCode >= 300) {
throw new PublishException(JSON.stringify(context.httpBody), context);
}
}
else if (mode === 'close') {
throw new PublishException('Connection closed unexpectedly', context);
}
}
}