@securecall/client-component
Version:
SecureCall Core Web Component
1,465 lines (1,444 loc) • 71.6 kB
JavaScript
import { r as registerInstance, c as createEvent, h, H as Host } from './index-CHf4eHvJ.js';
import { L as Logger, C as CLIENT_COMP_VERSION } from './logger-CgjdSc_8.js';
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
const isObject = (obj) => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
if (oVal === undefined) {
return; // Skip undefined values
}
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(oVal);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
class RequestField {
order;
value;
readOnly;
hidden;
valid;
possibleValues;
mapping;
externalMapping;
label;
component;
min;
max;
active;
placeholder;
hideRelatedFields;
secure;
optional;
_name;
_owner;
constructor(owner, name) {
this._name = name;
this._owner = owner;
return new Proxy(this, {
set(target, prop, value, receiver) {
const propStr = prop.toString();
// Check if it's a public property (doesn't start with underscore)
if (!propStr.startsWith('_') && propStr in target) {
// Call propagate on the owner if it exists
if (target._owner && typeof target._owner.propagate === 'function') {
target._owner.propagate(target._name, propStr, value);
}
}
return Reflect.set(target, prop, value, receiver);
}
});
}
toJSON() {
return {
[this._name]: this.calculatedValue()
};
}
calculatedValue() {
const result = {};
for (const key in this) {
if (this.hasOwnProperty(key) // find properties
&& !key.startsWith('_') // remove private ones by naming convention
&& this[key] !== undefined) { // ignore undefined ones
result[key] = this[key];
}
}
return result;
}
}
class MetadataField {
_name;
_value;
constructor() {
this._name = 'metadata';
this._value = {};
return new Proxy(this, {
get(target, prop, receiver) {
const propStr = prop.toString();
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(target)).concat(Object.getOwnPropertyNames(target));
// console.log('** metadata get:', propStr, methods)
if (methods.includes(propStr)) {
return Reflect.get(target, prop, receiver);
}
else {
return target.rawValue[propStr];
}
},
set(target, prop, v, receiver) {
const propStr = prop.toString();
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(target)).concat(Object.getOwnPropertyNames(target));
// console.log('** metadata set:', propStr, methods)
if (methods.includes(propStr)) {
return Reflect.set(target, prop, v, receiver);
}
else {
target.rawValue[propStr] = v;
return true;
}
}
});
}
toJSON() {
return {
[this._name]: this.calculatedValue()
};
}
calculatedValue() {
return { value: this._value };
}
get rawValue() {
return this._value;
}
}
class RequestFieldList {
_fields;
_uiFields;
_level;
constructor(uiFields, fieldLevel = FieldLevelType.Instance) {
this._uiFields = uiFields;
this._level = fieldLevel;
this._fields = { metadata: new MetadataField() };
return new Proxy(this, {
get(target, prop, receiver) {
const propStr = prop.toString();
if (target.parent.fieldNames.includes(propStr)) {
if (!target.rawFields[propStr]) {
target.rawFields[propStr] = new RequestField(target, propStr);
}
return target.rawFields[propStr];
}
return Reflect.get(target, prop, receiver);
},
});
}
get parent() {
return this._uiFields;
}
get rawFields() {
return this._fields;
}
addNewField(name, config) {
if (!this.parent.fieldNames.includes(name)) {
this.parent.fieldNames.push(name);
Object.entries(config).forEach(([k, v]) => {
this[name][k] = v;
});
}
else {
throw new Error(`Field ${name} already exists in the request fields list`);
}
}
propagate(fieldName, prop, value) {
if (this.parent.fieldNames.includes(fieldName)) {
if (this._level === FieldLevelType.Instance) {
this.propagateValue(this.parent.session, fieldName, prop, value);
}
else if (this._level === FieldLevelType.Session) {
this.propagateValue(this.parent.transaction, fieldName, prop, value);
}
}
}
propagateValue(fieldList, fieldName, prop, value) {
if (fieldList.rawFields[fieldName]) {
fieldList[fieldName][prop] = value;
}
}
calculatedFields() {
return Object.fromEntries(Object.entries(this._fields)
.filter(([key]) => this.parent.fieldNames.includes(key))
.map(([fieldName, field]) => [
fieldName,
field.calculatedValue()
]));
}
}
var FieldLevelType;
(function (FieldLevelType) {
FieldLevelType["Instance"] = "instance";
FieldLevelType["Session"] = "session";
FieldLevelType["Transaction"] = "transaction";
})(FieldLevelType || (FieldLevelType = {}));
class RequestFieldGroup {
_defaults;
_instance;
_session;
_transaction;
_fieldNames;
constructor(defaults) {
this._defaults = JSON.parse(JSON.stringify(defaults));
this._fieldNames = Object.keys(defaults);
this._instance = new RequestFieldList(this, FieldLevelType.Instance);
this._session = new RequestFieldList(this, FieldLevelType.Session);
this._transaction = new RequestFieldList(this, FieldLevelType.Transaction);
return new Proxy(this, {
get(target, prop, receiver) {
const propStr = prop.toString();
if (target.fieldNames.includes(propStr)) {
return target.instance[propStr];
}
return Reflect.get(target, prop, receiver);
},
});
}
get fieldNames() {
return this._fieldNames;
}
get instance() {
return this._instance;
}
get session() {
return this._session;
}
get transaction() {
return this._transaction;
}
addNewField(name, config) {
this._instance.addNewField(name, config);
}
processSecureData(data) {
const calc = this.calculatedFields();
//Reset the active flag for each secure
this._fieldNames.forEach(k => {
if (calc[k].secure)
this._transaction[k].active = undefined;
});
Object.entries(data).forEach(([k, v]) => {
let f = this._transaction[k];
Object.entries(v).forEach(([k2, v2]) => {
f[k2] = v2;
});
});
}
clearNonSecureFields() {
const calc = this.calculatedFields();
this._fieldNames.forEach(k => {
if (!calc[k].secure)
this._transaction[k].value = undefined;
});
}
resetInstance() {
this._instance = new RequestFieldList(this, FieldLevelType.Instance);
}
resetSession() {
this._session = new RequestFieldList(this, FieldLevelType.Session);
}
resetTransaction() {
this._transaction = new RequestFieldList(this, FieldLevelType.Transaction);
}
calculatedFields() {
return mergeDeep(this._defaults, this._instance.calculatedFields(), this._session.calculatedFields(), this._transaction.calculatedFields());
}
asComponentChildren() {
return Object.entries(this.calculatedFields())
.filter(([_k, v]) => !v.hidden)
.sort(([_ak, av], [_bk, bv]) => {
const orderA = av.order === undefined ? 1000 : av.order;
const orderB = bv.order === undefined ? 1000 : bv.order;
return orderA - orderB;
})
.map(([k, v]) => {
const { component, ...config } = v;
return {
name: k,
component: component || 'string',
config
};
});
}
asAPITransaction() {
let fields = { metadata: {} };
Object.entries(this.calculatedFields())
.filter(([_k, v]) => !v.secure)
.filter(([_k, v]) => !(v.optional && (!v.value || v.value.trim() === '')))
.forEach(([k, v]) => {
if (v.mapping) {
let mapping = v.mapping.split('.');
if (mapping[0] === 'metadata') {
fields.metadata[mapping[1]] = v.value;
}
else {
fields[mapping[0]] = v.value;
}
}
else {
if (k === 'metadata') {
fields[k] = { ...fields.metadata, ...v.value };
}
else {
fields[k] = v.value;
}
}
});
return fields;
}
}
const DEFAULT_REQUEST_FIELDS = {
transactionType: {
order: 10,
mapping: 'transaction_type',
label: 'Transaction Type',
placeholder: 'Select Transaction Type',
component: 'radio',
valid: true,
value: 'pay',
readOnly: false,
hidden: false,
possibleValues: { pay: 'Pay', pay_and_tokenise: 'Pay and Tokenise', tokenise: 'Tokenise' },
hideRelatedFields: {
pay: { amount: false, paymentReference: false, tokenReference: true },
pay_and_tokenise: { amount: false, paymentReference: false, tokenReference: false },
tokenise: { amount: true, paymentReference: true, tokenReference: false }
}
},
amount: {
order: 20,
label: 'Amount',
placeholder: 'Enter amount',
component: 'currency',
valid: false,
readOnly: false,
hidden: false,
min: 0.01,
max: 999999.99
},
paymentReference: {
order: 30,
mapping: 'payment_reference',
label: 'Payment Ref',
placeholder: 'Payment Reference',
component: 'string',
valid: false,
readOnly: false,
hidden: false
},
tokenReference: {
order: 40,
mapping: 'token_reference',
label: 'Customer Ref',
placeholder: 'Customer ID or Reference',
component: 'string',
valid: false,
readOnly: false,
hidden: false
},
nameOnCard: {
order: 50,
mapping: 'name_on_card',
label: 'Name On Card',
component: 'string',
valid: false,
readOnly: false,
hidden: false
},
gatewayName: {
order: 60,
mapping: 'metadata.gateway_name',
label: 'Gateway',
component: 'select',
valid: false,
readOnly: false,
hidden: true,
possibleValues: { default: 'Default Gateway' }
},
pan: {
order: 100,
label: 'Card Number',
component: 'secure-pan',
valid: false,
secure: true,
readOnly: false,
hidden: false
},
expiry: {
order: 110,
label: 'Expiry Date',
component: 'secure',
valid: false,
secure: true,
readOnly: false,
hidden: false
},
cvv: {
order: 120,
label: 'CVV',
component: 'secure',
secure: true,
valid: false,
readOnly: false,
hidden: false
},
metadata: { order: 1000, label: 'Metadata', component: 'metadata', valid: true, readOnly: false, hidden: true },
};
var CURRENT_STATE_T;
(function (CURRENT_STATE_T) {
CURRENT_STATE_T["NotAuthenticated"] = "NotAuthenticated";
CURRENT_STATE_T["Idle"] = "Idle";
CURRENT_STATE_T["Securing"] = "Securing";
CURRENT_STATE_T["Secured"] = "Secured";
CURRENT_STATE_T["Submitting"] = "Submitting";
CURRENT_STATE_T["Success"] = "Success";
CURRENT_STATE_T["Failure"] = "Failure";
})(CURRENT_STATE_T || (CURRENT_STATE_T = {}));
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/parse.ts
function getBytes(stream, onChunk) {
return __async(this, null, function* () {
const reader = stream.getReader();
let result;
while (!(result = yield reader.read()).done) {
onChunk(result.value);
}
reader.releaseLock();
});
}
function getLines(onLine, onTimeout, timeoutMs = 3e4) {
let buffer;
let position;
let fieldLength;
let discardTrailingNewline = false;
let timeoutId;
let keepAliveReceived = false;
const resetTimeout = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (onTimeout && !keepAliveReceived) {
timeoutId = setTimeout(() => {
onTimeout();
}, timeoutMs);
}
};
const clearTimeoutIfExists = () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = void 0;
}
};
resetTimeout();
return function onChunk(arr) {
if (buffer === void 0) {
buffer = arr;
position = 0;
fieldLength = -1;
keepAliveReceived = false;
resetTimeout();
} else {
buffer = concat(buffer, arr);
}
const bufLength = buffer.length;
let lineStart = 0;
while (position < bufLength) {
if (discardTrailingNewline) {
if (buffer[position] === 10 /* NewLine */) {
lineStart = ++position;
}
discardTrailingNewline = false;
}
let lineEnd = -1;
for (; position < bufLength && lineEnd === -1; ++position) {
switch (buffer[position]) {
case 58 /* Colon */:
if (fieldLength === -1) {
fieldLength = position - lineStart;
keepAliveReceived = true;
clearTimeoutIfExists();
}
break;
// @ts-ignore:7029 \r case below should fallthrough to \n:
case 13 /* CarriageReturn */:
discardTrailingNewline = true;
case 10 /* NewLine */:
lineEnd = position;
break;
}
}
if (lineEnd === -1) {
break;
}
onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
lineStart = position;
fieldLength = -1;
keepAliveReceived = false;
resetTimeout();
}
if (lineStart === bufLength) {
buffer = void 0;
} else if (lineStart !== 0) {
buffer = buffer.subarray(lineStart);
position -= lineStart;
}
};
}
function getMessages(onId, onRetry, onMessage) {
let message = newMessage();
const decoder = new TextDecoder();
return function onLine(line, fieldLength) {
if (line.length === 0) {
onMessage == null ? void 0 : onMessage(message);
message = newMessage();
} else if (fieldLength > 0) {
const field = decoder.decode(line.subarray(0, fieldLength));
const valueOffset = fieldLength + (line[fieldLength + 1] === 32 /* Space */ ? 2 : 1);
const value = decoder.decode(line.subarray(valueOffset));
switch (field) {
case "data":
message.data = message.data ? message.data + "\n" + value : value;
break;
case "event":
message.event = value;
break;
case "id":
onId(message.id = value);
break;
case "retry":
const retry = parseInt(value, 10);
if (!isNaN(retry)) {
onRetry(message.retry = retry);
}
break;
}
}
};
}
function concat(a, b) {
const res = new Uint8Array(a.length + b.length);
res.set(a);
res.set(b, a.length);
return res;
}
function newMessage() {
return {
data: "",
event: "",
id: "",
retry: void 0
};
}
// src/errorClasses.ts
var RetriableError = class extends Error {
constructor(message) {
super(message);
this.name = "RetriableError";
}
};
var HTTPError = class extends Error {
constructor(code, message) {
super(message);
this.name = "HTTPError";
this.code = code;
}
};
var HTTPClientError = class extends HTTPError {
constructor(code, message) {
super(code, message);
this.name = "HTTPClientError";
}
};
var FailedRetriesError = class extends Error {
constructor(message) {
super(message);
this.name = "FailedRetriesError";
}
};
var NotConnectedError = class extends Error {
constructor(message) {
super(message);
this.name = "NotConnectedError";
}
};
// src/fetch.ts
var EventStreamContentType = "text/event-stream";
var DefaultRetryInterval = 1e3;
var LastEventId = "last-event-id";
var HeartbeatTimer = 0;
function fetchEventSource(input, _a) {
var _b = _a, {
signal: inputSignal,
headers: inputHeaders,
onopen: inputOnOpen,
onmessage,
onclose,
onerror,
fetch: inputFetch,
heartbeat: inputHeartbeat
} = _b, rest = __objRest(_b, [
"signal",
"headers",
"onopen",
"onmessage",
"onclose",
"onerror",
"fetch",
"heartbeat"
]);
return new Promise((resolve, reject) => {
const headers = __spreadValues({}, inputHeaders);
if (!headers.accept) {
headers.accept = EventStreamContentType;
}
let curRequestController;
let retryInterval = DefaultRetryInterval;
let retryTimer = 0;
function dispose() {
window.clearTimeout(retryTimer);
curRequestController.abort();
}
inputSignal == null ? void 0 : inputSignal.addEventListener("abort", () => {
dispose();
resolve();
});
const heartbeat = inputHeartbeat != null ? inputHeartbeat : HeartbeatTimer;
const fetch2 = inputFetch != null ? inputFetch : window.fetch;
const onopen = inputOnOpen != null ? inputOnOpen : defaultOnOpen;
function create() {
return __async(this, null, function* () {
var _a2, _b2;
curRequestController = new AbortController();
try {
const response = yield fetch2(input, __spreadProps(__spreadValues({}, rest), {
headers,
signal: curRequestController.signal
}));
yield onopen(response);
yield getBytes(response.body, getLines(
getMessages((id) => {
if (id) {
headers[LastEventId] = id;
} else {
delete headers[LastEventId];
}
}, (retry) => {
retryInterval = retry;
}, onmessage),
heartbeat > 0 ? () => {
curRequestController.abort("timeout");
} : void 0,
heartbeat
));
onclose == null ? void 0 : onclose();
dispose();
resolve();
} catch (err) {
if (!curRequestController.signal.aborted) {
try {
const interval = (_a2 = onerror == null ? void 0 : onerror(err)) != null ? _a2 : retryInterval;
window.clearTimeout(retryTimer);
retryTimer = window.setTimeout(create, interval);
} catch (innerErr) {
dispose();
reject(innerErr);
}
} else if (curRequestController.signal.reason === "timeout") {
try {
const timeoutError = new RetriableError(`Timeout: No traffic received within ${heartbeat}ms`);
const interval = (_b2 = onerror == null ? void 0 : onerror(timeoutError)) != null ? _b2 : retryInterval;
window.clearTimeout(retryTimer);
retryTimer = window.setTimeout(create, interval);
} catch (innerErr) {
dispose();
reject(innerErr);
}
}
}
});
}
create();
});
}
function defaultOnOpen(response) {
const contentType = response.headers.get("content-type");
if (!(contentType == null ? void 0 : contentType.startsWith(EventStreamContentType))) {
throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
}
}
// src/constants.ts
var LOGIN_PATH = "/v1/login";
var SECURE_PATH = "/v1/secure";
var RELEASE_PATH = "/v1/release";
var EVENTS_PATH = "/v1/events";
var RESET_PATH = "/v1/reset";
var SUBMIT_PATH = "/v1/submit";
var LOGOUT_PATH = "/v1/logout";
var TELEPHONY_PATH = "/v1/telephony";
var DEFAULT_HEADERS = { "Content-Type": "application/json" };
var MAX_RETRIES = 2;
var HEARTBEAT_TIMEOUT = 45e3;
var HTTP_STATUS_TEXT = {
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
408: "Request Timeout",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable"
};
// src/events.ts
var AuthenticateEvent = class extends Event {
constructor(success, reason, code) {
super("authentication");
this.success = success;
this.reason = reason;
this.code = code;
if (reason === void 0 && this.code !== void 0) {
this.reason = code == null ? void 0 : code.toString();
}
}
};
var StreamEvent = class _StreamEvent extends Event {
static createEvent(type, data) {
let evt;
switch (type) {
case "configuration":
evt = new ConfigurationEvent(data);
break;
case "status":
evt = new SessionEvent(data);
break;
case "telephony":
evt = new TelephonyEvent(data);
break;
default:
evt = new _StreamEvent(type, data);
break;
}
return evt;
}
constructor(type, data) {
super(type);
this.detail = data;
}
};
var ConfigurationEvent = class extends StreamEvent {
constructor(data) {
super("configuration", data);
}
get settings() {
return this.detail.settings;
}
get gateways() {
return this.detail.gateways;
}
};
var SessionEvent = class extends StreamEvent {
constructor(data) {
super("session", data);
}
get call_data() {
return this.detail.call_data;
}
get secure_data() {
return this.detail.secure_data;
}
get transaction_data() {
return this.detail.transaction_data;
}
get state() {
return this.detail.state;
}
};
var TelephonyEvent = class extends StreamEvent {
constructor(data) {
super("telephony", data);
}
get session() {
return this.detail.session;
}
get command() {
return this.detail.command;
}
get dnis() {
return this.detail.dnis;
}
};
// src/version.ts
var CLIENT_LIB_JS_VERSION = "1.0.0";
// src/index.ts
var SecureCallClientAPI = class extends EventTarget {
/**
*
* @param apiLocation - The URL prefix for all communication to the SecureCall Client API
* @param callerId - added to all logging if passed
*/
constructor(apiLocation, callerId) {
super();
this.useCookie = false;
this.connected = false;
this.instanceId = Math.floor(Math.random() * 1e8);
this.popupWindow = null;
this.webexFlag = false;
this.retryCount = 0;
this.logOutput = console;
this.isDebugActive = false;
this.apiLocation = apiLocation;
this.logPrefix = `SecureCallClientAPI(${CLIENT_LIB_JS_VERSION}/${this.instanceId})`;
if (callerId) {
this.logPrefix = `${this.logPrefix}->${callerId}`;
}
this.debug = true;
}
/**
* @returns true if the event stream connection is established
*/
get isConnected() {
return this.connected;
}
/**
*
* @param isDebug - set to true to have log output go to the logger or false to stop logging
*/
set debug(isDebug) {
this.isDebugActive = isDebug;
if (isDebug) {
const timestamp = function() {
};
timestamp.toString = function() {
return (/* @__PURE__ */ new Date()).toISOString();
};
this.log = this.logOutput.debug.bind(this.logOutput, timestamp.toString(), this.logPrefix);
} else {
this.log = function() {
};
}
}
/**
* @returns true if debug logging is active
*/
get debug() {
return this.isDebugActive;
}
/**
*
* @param l - any object which exposes a function called 'debug' equivalent to console.debug
*/
set logger(l) {
this.logOutput = l;
this.debug = this.isDebugActive;
}
/**
*
* @param email - email address of the user
* @param password - password of the user. This could be a Webex Org ID, SecureCall API Key or a password
* @param useCookie - true if cookies will be used for all API requests or false to send the email/password each time
*/
authenticate(email, password, useCookie) {
return __async(this, null, function* () {
this.log("authenticate");
this.email = email != null ? email : this.email;
this.password = password != null ? password : this.password;
if (typeof useCookie != "undefined") {
this.useCookie = useCookie;
}
return this.connect();
});
}
/**
* login uses the email/password to log into the Client API. Typically only used when cookies are used.
*/
login() {
return __async(this, null, function* () {
let response;
try {
this.log("login");
response = yield fetch(this.apiLocation + LOGIN_PATH, {
method: "POST",
credentials: "include",
headers: this.clientAPIHeaders()
});
this.log("login", "response", response);
if (response.redirected) {
return this.handleRedirect(response);
} else if (response.ok) {
return this.connectEventStream();
}
} catch (error) {
this.log("login", "error", error.message);
response = new Response(null, { status: 400, statusText: error.message });
}
this.asyncDispatchEvent(new AuthenticateEvent(false, response.statusText, response.status));
this.throwHTTPError(response.status, response.statusText);
});
}
/**
* Logs out of the SecureCall Client API and disconnects the event stream
*/
logout() {
return __async(this, null, function* () {
this.log("logout");
if (!this.connected) {
throw new NotConnectedError();
}
this.disconnect();
let response;
try {
response = yield fetch(this.apiLocation + LOGOUT_PATH, {
method: "POST",
credentials: "include",
headers: this.clientAPIHeaders()
});
this.log("logout", "response", response);
if (response.redirected) {
return this.handleRedirect(response);
} else if (response.ok) {
return;
}
} catch (error) {
this.log("logout", "error", error.message);
response = new Response(null, { status: 400, statusText: error.message });
}
this.throwHTTPError(response.status, response.statusText);
});
}
/**
* Attempts to secure the call. All updates come through the event stream and will be emitted as Events
*/
secure(body) {
return __async(this, null, function* () {
this.log("secure");
if (!this.connected) {
throw new NotConnectedError();
}
let response;
try {
response = yield fetch(this.apiLocation + SECURE_PATH, {
method: "POST",
credentials: "include",
headers: this.clientAPIHeaders(),
body: JSON.stringify(body != null ? body : {})
});
this.log("secure", "response", "", response);
if (response.redirected) {
this.handleRedirect(response);
return;
} else if (response.ok) {
return;
} else if (response.status === 401 || response.status === 403) {
this.asyncDispatchEvent(new AuthenticateEvent(false, response.statusText, response.status));
}
} catch (error) {
this.log("secure", "error", error.message);
response = new Response(null, { status: 400, statusText: error.message });
}
this.throwHTTPError(response.status, response.statusText);
});
}
/**
* Attempts to release the call. All updates come through the event stream and will be emitted as Events
*/
release() {
return __async(this, null, function* () {
this.log("release");
if (!this.connected) {
throw new NotConnectedError();
}
let response;
try {
response = yield fetch(this.apiLocation + RELEASE_PATH, {
method: "POST",
credentials: "include",
headers: this.clientAPIHeaders()
});
this.log("release", "response", "", response);
if (response.redirected) {
this.handleRedirect(response);
return;
} else if (response.ok) {
return;
} else if (response.status === 401 || response.status === 403) {
this.asyncDispatchEvent(new AuthenticateEvent(false, response.statusText, response.status));
}
} catch (error) {
this.log("release", "error", error.message);
response = new Response(null, { status: 400, statusText: error.message });
}
this.throwHTTPError(response.status, response.statusText);
});
}
/**
*
* @param v - if empty, will attempt to reset all the PCI fields, if a string will attempt to reset just that field or can be an array of field name
*/
reset(v) {
return __async(this, null, function* () {
let data;
this.log("reset");
if (!this.connected) {
throw new NotConnectedError();
}
if (v === void 0) {
data = ["all"];
} else if (typeof v === "string") {
data = [v];
} else {
data = v;
}
let response;
try {
response = yield fetch(this.apiLocation + RESET_PATH, {
method: "POST",
credentials: "include",
headers: this.clientAPIHeaders(),
body: JSON.stringify({ fields: data })
});
this.log("reset", "response", response);
if (response.redirected) {
return this.handleRedirect(response);
} else if (response.ok) {
return;
} else if (response.status === 401 || response.status === 403) {
this.asyncDispatchEvent(new AuthenticateEvent(false, response.statusText, response.status));
}
} catch (error) {
this.log("reset", "error", error.message);
response = new Response(null, { status: 400, statusText: error.message });
}
this.throwHTTPError(response.status, response.statusText);
});
}
/**
*
* @param data - submits payment data
*/
submit(data) {
return __async(this, null, function* () {
this.log("submit");
if (!this.connected) {
throw new NotConnectedError();
}
if (data === void 0) {
throw new Error("no parameter");
}
let response;
try {
this.log("submit", data);
response = yield fetch(this.apiLocation + SUBMIT_PATH, {
method: "POST",
credentials: "include",
headers: this.clientAPIHeaders(),
body: JSON.stringify(data)
});
this.log("submit", "response", "", response);
if (response.redirected) {
return this.handleRedirect(response);
} else if (response.ok) {
return;
} else if (response.status === 401 || response.status === 403) {
this.asyncDispatchEvent(new AuthenticateEvent(false, response.statusText, response.status));
}
} catch (error) {
this.log("submit", "error", error.message);
response = new Response(null, { status: 400, statusText: error.message });
}
this.throwHTTPError(response.status, response.statusText);
});
}
/**
*
* @param data - update telephony state for Client Telephony
*/
telephony(data) {
return __async(this, null, function* () {
this.log("telephony");
if (!this.connected) {
throw new NotConnectedError();
}
if (data === void 0) {
throw new Error("no parameter");
}
let response;
try {
response = yield fetch(this.apiLocation + TELEPHONY_PATH, {
method: "POST",
credentials: "include",
headers: this.clientAPIHeaders(),
body: JSON.stringify(data)
});
this.log("telephony", "response", "", response);
if (response.redirected) {
return this.handleRedirect(response);
} else if (response.ok) {
return;
} else if (response.status === 401 || response.status === 403) {
this.asyncDispatchEvent(new AuthenticateEvent(false, response.statusText, response.status));
}
} catch (error) {
this.log("telephony", "error", error.message);
response = new Response(null, { status: 400, statusText: error.message });
}
this.throwHTTPError(response.status, response.statusText);
});
}
/**
* connects the event stream using email/password/useCookie
*/
connect() {
if (!this.useCookie && !this.email && !this.password) {
throw new Error("email and password need to be set for useCookie to be false");
}
if (this.email && !this.password) {
throw new Error("password needs to be set");
}
if (this.useCookie) {
return this.login();
} else {
return this.connectEventStream();
}
}
/**
* disconnects the event stream connection
*/
disconnect() {
var _a;
this.log("disconnect");
(_a = this.abortController) == null ? void 0 : _a.abort();
}
handleAPIResponse(response) {
this.log("handleAPIResponse");
if (response.redirected) {
this.handleRedirect(response);
} else if (response.ok) {
this.log("handleAPIResponse", "doLogin was successful", response);
return this.connectEventStream();
} else {
this.throwHTTPError(response.status, response.statusText);
}
}
handleRedirect(response) {
this.log("handleRedirect", response.url);
this.popupWindow = window.open(response.url, "popup", "width=600,height=600");
if (this.popupWindow) {
let timer = setInterval(() => {
var _a;
if ((_a = this.popupWindow) == null ? void 0 : _a.closed) {
clearInterval(timer);
this.log("handleRedirect", "detected popup window was closed");
this.asyncDispatchEvent(new AuthenticateEvent(false, "popup window closed"));
}
}, 500);
window.addEventListener("message", (event) => {
if (event.data === "webexLoginSuccess") {
clearInterval(timer);
this.log("handleRedirect", "window eventListener", "Webex login was successful");
this.connectEventStream();
}
}, { once: true });
}
}
connectEventStream() {
return __async(this, null, function* () {
var _a;
this.log("connectEventStream");
(_a = this.abortController) == null ? void 0 : _a.abort();
this.abortController = new AbortController();
const signal = this.abortController.signal;
yield fetchEventSource(this.apiLocation + EVENTS_PATH, {
method: "GET",
credentials: this.shouldSendCredentials() ? "same-origin" : "include",
headers: this.eventSourceHeaders(),
signal,
onopen: this.onEventStreamOpen.bind(this),
onmessage: this.onEventStreamMessage.bind(this),
onclose: this.onEventStreamClose.bind(this),
onerror: this.onEventStreamError.bind(this),
heartbeat: HEARTBEAT_TIMEOUT
});
});
}
onEventStreamOpen(response) {
return __async(this, null, function* () {
var _a;
this.log("onEventStreamOpen", "status=", response.status);
if (response.redirected) {
this.log("onEventStreamOpen", "response.redirected", response);
this.webexFlag = true;
return this.handleRedirect(response);
} else if (response.ok) {
if ((_a = response.headers.get("content-type")) == null ? void 0 : _a.startsWith(EventStreamContentType)) {
this.dispatchEvent(new AuthenticateEvent(true));
this.connected = true;
this.log("onEventStreamOpen", "text/event-stream response.ok", response);
} else {
this.log("onEventStreamOpen", "text/html response.ok", response);
throw new Error("Received an unexpected text/html page");
}
} else if (response.status >= 400 && response.status <= 499)
throw new HTTPClientError(response.status, response.statusText);
else
throw new RetriableError("onopen retriable");
});
}
onEventStreamMessage(msg) {
this.log("onEventStreamMessage", "event", msg.event, msg);
this.asyncDispatchEvent(StreamEvent.createEvent(msg.event, JSON.parse(msg.data)));
}
onEventStreamClose() {
this.connected = false;
if (this.webexFlag) {
this.log("onEventStreamClose", "doing_popup so ignore");
this.webexFlag = false;
} else {
this.log("onEventStreamClose");
throw new RetriableError("close");
}
}
onEventStreamError(error) {
this.log("onEventStreamError", error);
if (error instanceof RetriableError) {
if (this.retryCount < MAX_RETRIES) {
const delay = Math.pow(2, this.retryCount) * 1e3;
this.retryCount++;
this.log(`Retrying in ${delay} milliseconds...`);
return delay;
} else {
this.asyncDispatchEvent(new AuthenticateEvent(false, "no more retries"));
this.retryCount = 0;
throw new FailedRetriesError();
}
} else {
this.asyncDispatchEvent(new AuthenticateEvent(false, error.message, error == null ? void 0 : error.code));
this.retryCount = 0;
throw error;
}
}
asyncDispatchEvent(evt) {
return __async(this, null, function* () {
this.dispatchEvent(evt);
});
}
shouldSendCredentials() {
return this.email && this.password;
}
authenticationHeader() {
if (this.email && this.password) {
return { Authorization: "Basic " + btoa(this.email + ":" + this.password) };
} else {
return {};
}
}
eventSourceHeaders() {
return __spreadValues(__spreadProps(__spreadValues({}, this.authenticationHeader()), { Accept: "text/event-stream, text/html" }), this.logHeaders());
}
clientAPIHeaders() {
return __spreadValues(__spreadValues(__spreadValues({}, DEFAULT_HEADERS), this.authenticationHeader()), this.logHeaders());
}
logHeaders() {
return { "X-SecureCall-Client-Identifier": this.logPrefix };
}
throwHTTPError(status, text) {
if (!text) {
text = this.lookupHTTPText(status);
}
if (status >= 400 && status <= 499)
throw new HTTPClientError(status, text);
else
throw new HTTPError(status, text);
}
// HTTP/2 doesn't return statusText in the response so we need to do a lookup
lookupHTTPText(status) {
return HTTP_STATUS_TEXT[status] || "Unknown";
}
};
class ResponseFields {
_defaults;
_result;
_fieldNames;
constructor(defaults) {
this._defaults = JSON.parse(JSON.stringify(defaults));
this._fieldNames = Object.keys(defaults);
this._result = {};
}
get defaults() {
return this._defaults;
}
toJSON() {
return this.calculatedFields();
}
processResponse(data) {
if (data === undefined || data === null) {
return;
}
this._fieldNames.forEach(k => {
const mapping = this._defaults[k].mapping || `response.${k}`;
let maps = mapping.split('.');
const val = maps.reduce((obj, key) => obj?.[key], data);
if (val !== undefined) {
this._result[k] = { value: val };
}
});
}
get fieldNames() {
return this._fieldNames;
}
asComponentChildren(success) {
return Object.entries(this.calculatedFields())
.filter(([_k, v]) => {
return (success) ? v.showOnSuccess : v.showOnFailure;
})
.filter(([_k, v]) => {
return (!(v.ignoreIfEmpty && (v.value === null || v.value === undefined || v.value === '')));
})
.sort(([_ak, av], [_bk, bv]) => {
const orderA = av.order === undefined ? 1000 : av.order;
const orderB = bv.order === undefined ? 1000 : bv.order;
return orderA - orderB;
})
.map(([k, v]) => {
return {
name: k,
label: v.label,
value: v.value,
action: v.action,
component: v.component || 'field',
};
});
}
calculatedFields() {
return mergeDeep(this._defaults, this._result);
}
}
const DEFAULT_RESPONSE_FIELDS = {
transactionId: {
order: 10,
label: 'Transaction ID',
mapping: 'response.transaction_id',
showOnSuccess: true
},
receipt: {
order: 20,
label: 'Receipt',
showOnSuccess: true
},
token: {
order: 30,
label: 'Token',
showOnSuccess: false
},
errorMessage: {
order: 100,
label: 'Error Message',
mapping: 'response.error_message',
showOnFailure: false
},
errorCode: {
order: 110,
label: 'Error Code',
mapping: 'response.error_code',
showOnFailure: false
},
anotherTransaction: {
order: 200,
label: 'Another Transaction',
action: 'retryClearCVV',
showOnSuccess: true,
showOnFailure: true,
component: 'button'
}
};
const PENDING_CALL_SECURE_TIMEOUT = 35000;
const PENDING_PAYMENT_TIMEOUT = 60000;
const AUTHENTICATION_RETRIES = 3;
const securecallClientCss = ":host{display:flex;flex-direction:column;align-items:center;width:fit-content;min-width:300px;margin:auto;--theme-background-color-dark:none;--theme-primary-color-dark:white;--theme-secondary-color-dark:#ccc;--theme-button-color-dark:rgba(4, 156, 196, 0.6);--theme-button-color-hover-dark:rgba(4, 156, 196, 1);--theme-button-color-disabled-dark:rgba(4, 156, 196, 0.3);--theme-text-disabled-color-dark:rgba(4, 156, 196, 1);--theme-border-color-dark:#ccc;--theme-border-color-disabled-dark:rgba(4, 156, 196, 0.3);--theme-input-active-background-dark:white;--theme-input-inactive-background-dark:#ccc;--theme-spinner-border-dark:rgba(255, 255, 255, 0.3);--theme-spinner-color-dark:rgba(4, 156, 196, 1);--theme-background-color-light:none;--theme-primary-color-light:#333;--theme-secondary-color-light:#ccc;--theme-button-color-light:rgba(4, 156, 196, 0.6);--theme-button-color-hover-light:rgba(4, 156, 196, 1);--theme-button-color-disabled-light:rgba(4, 156, 196, 0.3);--theme-text-disabled-color-light:rgba(4, 156, 196, 0.6);--theme-border-color-light:#ccc;--theme-border-color-disabled-light:rgba(4, 156, 196, 0.3);--theme-input-active-background-light:white;--theme-input-inactive-background-light:#ccc;--theme-spinner-border-light:rgba(0, 0, 0, 0.3);--theme-spinner-color-light:rgba(4, 156, 196, 1)}.theme-dark{width:100%;background-color:var(--theme-background-color-dark);--theme-primary-color:var(--theme-primary-color-dark);--theme-secondary-color:var(--theme-secondary-color-dark);--theme-button-color:var(--theme-button-color-dark);--theme-button-color-hover:var(--theme-button-color-hover-dark);--theme-button-color-disabled:var(--theme-button-color-disabled-dark);--theme-text-disabled-color:var(--theme-text-disabled-color-dark);--theme-border-color:var(--theme-border-color-dark);--theme-border-color-disabled:var(--theme-border-color-disabled-dark);--theme-input-active-background:var(--theme-input-active-background-dark);--theme-input-inactive-background:var(--theme-input-inactive-background-dark);--theme-spinner-border:var(--theme-spinner-border-dark);--theme-spinner-color:var(--theme-spinner-color-dark)}.theme-light{width:100%;background-color:var(--theme-background-color-light);--theme-primary-color:var(--theme-primary-color-light);--theme-secondary-color:var(--theme-secondary-color-light);--theme-button-color:var(--theme-button-color-light);--theme-button-color-hover:var(--theme-button-color-hover-light);--theme-button-color-disabled:var(--theme-button-color-disabled-dark);--theme-text-disabled-color:var(--theme-text-disabled-color-light);--theme-border-color:var(--theme-border-color-light);--theme-border-color-disabled:var(--theme-border-color-disabled-light);--theme-input-active-background:var(--theme-input-active-background-light);--theme-input-inactive-background:var(--theme-input-inactive-background-light);--theme-spinner-border:var(--theme-spinner-border-light);--theme-spinner-color:var(--theme-spinner-color-light)}.component-button{width:fit-content;padding:10px 20px;font-size:16px;color:var(--theme-primary-color);background-color:var(--theme-button-color);border:1px solid var(--theme-secondary-color);border-radius:5px;transition:background-color 0.3s ease, border-color 0.3s ease;cursor:pointer}.component-button:hover{background-color:var(--theme-button-color-hover);border-color:var(--theme-button-color-hover)}.component-button:disabled{background-color:var(--theme-button-color-disabled);border-color:var(--theme-border-color-disabled);color:var(--theme-text-disabled-color);cursor:not-allowed;pointer-events:none}.secure-form{text-align:center}.response-result-container{text-align:center;font-size:1.2rem;font-weight:bold}.loading-spinner{display:flex;justify-content:center;align-items:center;height:var(--spinner-container-size, 50px)}.spinner{position:relative;width:var(--spinner-container-size, 50px);height:var(--spinner-container-size, 50px)}.spinner-circle{border:4px solid var(--theme-spinner-border);border-top:4px solid var(--theme-spinner-color);border-radius:50%;width:var(--spinner-circle-size, 20px);height:var(--spinner-circle-size, 20px);animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}";
const SecurecallClient = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.authenticationSuccess = createEvent(this, "authenticationSuccess");
this.authenticationFailure = createEvent(this, "authenticationFailure");
this.configurationChanged = createEvent(this, "configurationChanged");
this.callSecured = createEvent(this, "callSecured");
this.callEnded = createEvent(this, "callEnded");
this.transactionSubmitted = createEvent(this, "transactionSubmitted");
this.transactionSubmitting = createEvent(this, "transactionSubmitting");
this.transactionSuccess = createEvent(this, "transactionSuccess");
this.transactionFailure = createEvent(this, "transactionFailure");
this.loggedOut = createEvent(this, "loggedOut");
this.telephonyHold = createEvent(this, "telephonyHold");
this.telephonyRecover = createEvent(this, "telephonyRecover");
this.telephonyLegA = createEvent(this, "telephonyLegA");
this.telephonyLegB = createEvent(this, "telephonyLegB");