@noggin/elastic-noggin-sdk
Version:
Elastic Noggin SDK
441 lines (334 loc) • 9.96 kB
text/typescript
import { IEno, IField, II18n, Tip } from "./models/types";
import { Eno } from "./models/Eno";
import { hashSidStringify } from "./sidStringify";
import { cloneDeep, isArray, isEqual, pullAllBy } from "lodash";
import dataConstants from "./constants";
import shajs from "sha.js";
const EMPTY_NONCE_TYPES = [
"var",
"query/dimension",
"op/query",
"op/query/dimension",
"op/pull",
"op/merge",
"op/formula",
"op/session",
"op/watch/register",
"op/watch/unregister",
"op/auth/gen-token",
"op/auth/reset-token",
"op/auth/register",
];
export class EnoFactory {
private _proto: IEno = null;
private _patchTargetTip: Tip = null;
private _useEmptyNonce = false;
private _useTipNonce = false;
private _useKnownNonce: string|null = null;
constructor(typeOrEnoProto: Tip | IEno = null, security: Tip = null) {
if (typeOrEnoProto && typeof typeOrEnoProto !== "string") {
this.setProto(typeOrEnoProto);
return;
}
this.reset(typeOrEnoProto as Tip, security);
}
public makeEno(): Eno {
const isValid = this._isSettingValid();
if (!isValid) {
throw new Error(
"Eno Factory setting has not enough information to make an eno."
);
}
this._cleanField();
if (this._useEmptyNonce || this._emptyNonceRequired()) {
this._proto.source.nonce = '';
} else if (this._useTipNonce && this._patchTargetTip) {
this._proto.source.nonce = this._patchTargetTip;
} else if (this._useKnownNonce !== null) {
this._proto.source.nonce = this._useKnownNonce;
} else {
this._proto.source.nonce = this._getRandomNonce();
}
this._generateSid();
this._cleanTransaction();
this._proto.tip = this._patchTargetTip || this._proto.sid;
return new Eno(this._proto);
}
private _getRandomNonce() {
return Math.random() + "";
}
private _cleanTransaction() {
if (this._proto.clientT === null) {
this._resetClientT();
}
const clientT = this._proto.clientT;
clientT.sequence = clientT.sequence || 1;
clientT.createdDate = clientT.modifiedDate = new Date().valueOf();
this._proto.serverT = null;
}
private _emptyNonceRequired(): boolean {
return EMPTY_NONCE_TYPES.indexOf(this._proto.source.type) > -1;
}
private _generateSid() {
let sha256 = shajs("sha256");
hashSidStringify(sha256, this._proto.source);
this._proto.sid = sha256.digest("hex");
sha256 = null;
}
private _cleanField() {
this._proto.source.field = this._proto.source.field.filter((field) => {
return (
(field.value && field.value.length !== 0) ||
(field.i18n && _containsNonEmptyValue()) ||
field.formula
);
function _containsNonEmptyValue(): boolean {
let nonEmptyValueFound = false;
for (let i = 0; i < field.i18n.length; i++) {
if (field.i18n[i].value && field.i18n[i].value.length > 0) {
nonEmptyValueFound = true;
break;
}
}
return nonEmptyValueFound;
}
});
}
private _isSettingValid(): boolean {
return !(
this._proto.source === null ||
this._proto.source.type === null ||
this._proto.source.security === null
);
}
public reset(type: Tip = null, security: Tip = null): EnoFactory {
this._patchTargetTip = null;
this._useEmptyNonce = false;
this._useTipNonce = false;
this._useKnownNonce = null;
this._proto = {
source: {
deleted: false,
type,
security,
parent: [],
field: [],
nonce: this._getRandomNonce(),
},
tip: null,
sid: null,
serverT: null,
clientT: null,
};
return this;
}
public setProto(eno: IEno): EnoFactory {
this.reset();
if (eno.clientT) {
this._proto.clientT = cloneDeep(eno.clientT);
}
if (eno.source) {
this._proto.source = cloneDeep(eno.source);
}
return this;
}
public setProtoToPatch(eno: IEno): EnoFactory {
if (!eno.source) {
throw new Error("You can't patch acknowledgement");
}
this.reset();
this._patchTargetTip = eno.tip;
if (eno.clientT) {
this._proto.clientT = cloneDeep(eno.clientT);
this._proto.clientT.sequence++;
} else {
this._resetClientT();
this._proto.clientT.sequence = 1;
}
this._proto.source = cloneDeep(eno.source);
this._proto.source.parent = [eno.sid];
return this;
}
public resetFields(): EnoFactory {
this._proto.source.field = [];
return this;
}
public setWellKnownTip(tip: Tip): EnoFactory {
this._patchTargetTip = tip;
return this;
}
public setType(type: Tip): EnoFactory {
this._proto.source.field =
this._proto.source.type !== type ? [] : this._proto.source.field;
this._proto.source.type = type;
return this;
}
public useEmptyNonce(): EnoFactory {
this._useEmptyNonce = true;
return this;
}
public useTipNonce(): EnoFactory {
this._useTipNonce = true;
return this;
}
public useRandomNonce(): EnoFactory {
this._useTipNonce = false;
this._useEmptyNonce = false;
this._useKnownNonce = null;
return this;
}
public useKnownNonce(nonce: string|null): EnoFactory {
this._useKnownNonce = nonce;
return this;
}
public setI18nValue(fieldTip: Tip, lang: string, value: string[]) {
value = value.filter(this._normalizeValuesFilter);
let fieldFound = false;
for (let i = 0; i < this._proto.source.field.length; i++) {
const field = this._proto.source.field[i];
if (field.tip === fieldTip) {
fieldFound = true;
this._updateExistingI18n(this._proto.source.field[i], lang, value);
break;
}
}
if (fieldFound) {
return this;
}
this._proto.source.field.push({ tip: fieldTip, i18n: [{ lang, value }] });
return this;
}
private _updateExistingI18n(field: IField, lang: string, value: string[]) {
field.i18n = field.i18n || [];
if (field.value) {
field.i18n = [{ lang: dataConstants.LANG_DEFAULT, value: field.value }];
delete field.value;
}
if (!isArray(value) || value.length === 0) {
pullAllBy(field.i18n, [{ lang }], "lang");
return;
}
const i18nValue: II18n = field.i18n.find(
(i18n: II18n) => i18n.lang === lang
);
if (i18nValue && isEqual(i18nValue.value, value)) {
return;
}
// Invalidate the values for other locales when the default locale value is updated.
if (lang === dataConstants.LANG_DEFAULT) {
field.i18n = [{ lang, value }];
return;
}
let langFound = false;
for (const i18n of field.i18n) {
if (i18n.lang === lang) {
langFound = true;
i18n.value = value;
break;
}
}
if (!langFound) {
field.i18n.push({ lang, value });
}
}
// Not recommended to use this method to set i18n field
public setField(
newFieldOrTip: string | IField,
value?: string[]
): EnoFactory {
let newField =
typeof newFieldOrTip === "string"
? { tip: newFieldOrTip, value }
: newFieldOrTip;
newField = this._normalizeIField(newField);
let fieldFound = false;
for (let i = 0; i < this._proto.source.field.length; i++) {
const field = this._proto.source.field[i];
if (field.tip === newField.tip) {
fieldFound = true;
this._proto.source.field[i] = newField;
break;
}
}
if (fieldFound) {
return this;
}
this._proto.source.field.push(newField);
return this;
}
public setFieldFormula(fieldTip: Tip, formula: string): EnoFactory {
let newField: IField = { tip: fieldTip, formula: [formula] };
newField = this._normalizeIField(newField);
let fieldFound = false;
for (let i = 0; i < this._proto.source.field.length; i++) {
const field = this._proto.source.field[i];
if (field.tip === newField.tip) {
fieldFound = true;
this._proto.source.field[i] = newField;
break;
}
}
if (fieldFound) {
return this;
}
this._proto.source.field.push(newField);
return this;
}
private _normalizeIField(field: IField): IField {
if (!field.i18n && !field.formula) {
field.value =
field.value === null || field.value === undefined ? [] : field.value;
field.value = field.value.filter(this._normalizeValuesFilter);
return field;
}
if (field.i18n) {
field.i18n.forEach((i18n) => {
i18n.value =
i18n.value === null || i18n.value === undefined ? [] : i18n.value;
i18n.value = i18n.value.filter(this._normalizeValuesFilter);
});
}
return field;
}
private _normalizeValuesFilter(val: string): boolean {
return val !== null && val !== undefined;
}
public setFields(newFields: IField[]): EnoFactory {
newFields.forEach((newField) => {
this.setField(newField);
});
return this;
}
public setSecurity(security: Tip): EnoFactory {
this._proto.source.security = security;
return this;
}
public setDeleted(deleted: boolean): EnoFactory {
this._proto.source.deleted = deleted;
this.resetFields();
return this;
}
public setBranch(branch: Tip = dataConstants.BRANCH_MASTER): EnoFactory {
if (this._proto.clientT === null) {
this._resetClientT(branch);
return this;
}
this._proto.clientT.branch = branch;
return this;
}
private _resetClientT(branch: Tip = dataConstants.BRANCH_MASTER) {
this._proto.clientT = {
branch,
sequence: null,
createdDate: null,
modifiedDate: null,
};
}
public setSequence(sequence: number): EnoFactory {
if (this._proto.clientT === null) {
this._resetClientT();
}
this._proto.clientT.sequence = sequence;
return this;
}
}