efficy-u-rpc-api
Version:
The Efficy U - RPC API is developed for server side usage in a Node.js environment and also bundled for usage inside an Efficy browser session for client-side JSON RPC requests.
1,659 lines (1,644 loc) • 87 kB
JavaScript
var types = /*#__PURE__*/Object.freeze({
__proto__: null
});
function isRPCNamedOperation(obj) {
return typeof obj['#id'] === 'string' && typeof obj['@name'] === 'string' && Array.isArray(obj['@func']);
}
function isJsonApiResponse(response) {
return (typeof response === 'object' && response !== null &&
typeof response.data === 'object');
}
function isJsonApiErrorNode(err) {
return (typeof err === 'object' && err !== null &&
'detail' in err && typeof err.detail === 'string' &&
'extra' in err && typeof err.extra === 'object' &&
'id' in err && typeof err.id === 'string' &&
'title' in err && typeof err.title === 'string');
}
var dataguards = /*#__PURE__*/Object.freeze({
__proto__: null,
isJsonApiErrorNode: isJsonApiErrorNode,
isJsonApiResponse: isJsonApiResponse,
isRPCNamedOperation: isRPCNamedOperation
});
/**
* Standard System Table IDs and their names.
* Query: select stblKTable, stblName from SysTable where stblKTable > 0 order by stblKTable
*/
const SysTableIdList = {
10: 'SysTable',
20: 'SysCategory',
30: 'SysField',
40: 'SysRelation',
50: 'SysIndex',
60: 'SysDatabase',
70: 'SysFieldIndexing',
80: 'SysLicense',
90: 'SysChildDb',
100: 'SysReplication',
130: 'SysUnityBean',
140: 'SysCompositeBean',
160: 'SysQuery',
170: 'SysWorkflow',
180: 'SysScript',
190: 'SysStorage',
200: 'SysPanel',
230: 'SysCalendar',
250: 'SysForm',
260: 'SysFormSection',
270: 'SysSectionField',
330: 'SysToken',
340: 'SysInlineSql',
350: 'SysSetting',
360: 'SysWebhook',
500: 'SysKeyMapping',
510: 'SysTableMapping',
520: 'SysFieldMapping',
560: 'SysFunction',
570: 'SysTrigger',
580: 'SysView',
10000: 'User',
10010: 'UserGroup',
10020: 'UserRight',
10030: 'UserNotification',
10040: 'UserSecurity',
10050: 'UserSharing',
10060: 'UserSession',
10080: 'UserOption',
10090: 'UserAuthentication',
10100: 'ApiKey',
10120: 'SecureToken',
20000: 'LogExport',
21000: 'LogScript',
22000: 'LogCredential',
23000: 'TracerEvent',
23500: 'TracerJob',
23600: 'TracerRule',
24000: 'LogEvent',
25000: 'StagingOperation',
25500: 'StagingDifference',
25600: 'StagingDiffDetail',
26000: 'LogConnection',
30000: 'Synchro',
30500: 'SyncProfile',
30510: 'Synp_User',
30600: 'SyncState',
30700: 'SyncQueue',
30800: 'SyncLock',
31000: 'ScheduledTask',
31500: 'ScheduledTaskLog',
33000: 'DataExtraction',
34000: 'Pipeline',
35000: 'PipelineActivity',
35034: 'Pipl_Pipa',
36000: 'Batch',
36500: 'BatchRun',
36600: 'BatchReport',
36700: 'BatchReportEntry',
37000: 'TimeBasedQueue',
40000: 'Duplicate',
41000: 'DuplicateAnalysis',
41500: 'DuplicateEntity',
41541: 'Dupe_Dupa',
41700: 'DuplicateCode',
42000: 'DuplicateMatch',
42500: 'DuplicateMatchField',
42600: 'DuplicateMatchRule',
43000: 'SearchBoostScore',
50000: 'Setting',
51000: 'History',
52000: 'Lock',
53000: 'Translation',
54000: 'Label',
55000: 'RefDefinition',
56000: 'Reference',
56500: 'RefLabel',
57000: 'NotificationFeed',
57010: 'Noti_User',
59000: 'Layout',
59010: 'Layo_User',
60000: 'LayoutOperation',
61000: 'RequestAction',
62000: 'RequestActionChain',
62061: 'Rqac_Rqat',
70000: 'File',
71000: 'Phone',
72000: 'Address',
73000: 'Favorite',
80000: 'Partition',
81000: 'UniqueReference',
81010: 'Unrf_User',
82000: 'Currency',
82500: 'CurrencyRate',
83000: 'VatRate',
85000: 'IndustrialIndex',
86000: 'Bank',
87000: 'PaymentTerm',
88000: 'PaymentMode',
100000: 'Company',
100010: 'Comp_User',
100072: 'Comp_Addr',
100081: 'Comp_Unrf',
100083: 'Comp_Vatr',
100086: 'Comp_Bank',
100087: 'Comp_Pymt',
100088: 'Comp_Pymo',
100100: 'Comp_Comp',
100431: 'Comp_Vatc',
100600: 'Comp$InvIssuer',
100700: 'Comp$Customer',
100800: 'Comp$CreditRating',
101000: 'Contact',
101010: 'Cont_User',
101072: 'Cont_Addr',
101100: 'Cont_Comp',
101101: 'Cont_Cont',
101500: 'Cont$Employee',
101600: 'Cont$DataPrivacy',
102000: 'Lead',
102010: 'Lead_User',
102100: 'Lead_Comp',
102101: 'Lead_Cont',
102500: 'Lead$DataPrivacy',
150000: 'Product',
150010: 'Prod_User',
150082: 'Prod_Crcy',
150083: 'Prod_Vatr',
150100: 'Prod_Comp',
150101: 'Prod_Cont',
150150: 'Prod_Prod',
150500: 'ProductFamily',
150600: 'Prod$Account',
151000: 'KnowledgeBase',
151010: 'Kbas_User',
151101: 'Kbas_Cont',
151150: 'Kbas_Prod',
151151: 'Kbas_Kbas',
151600: 'KnowledgeBaseComment',
151610: 'Kbac_User',
152000: 'QuizQuestion',
152010: 'Qqst_User',
152500: 'QuizAnswer',
153000: 'OnlineHelp',
154000: 'Profile',
154010: 'Prof_User',
154100: 'Prof_Comp',
154101: 'Prof_Cont',
154154: 'Prof_Prof',
154500: 'Prof$Emailing',
156000: 'GameMetric',
157000: 'GameTrophy',
157010: 'Trpy_User',
157011: 'Trpy_Plyr',
159000: 'RequestQualification',
160000: 'Consent',
160101: 'Cnst_Cont',
160102: 'Cnst_Lead',
200000: 'Project',
200010: 'Proj_User',
200072: 'Proj_Addr',
200100: 'Proj_Comp',
200101: 'Proj_Cont',
200150: 'Proj_Prod',
200154: 'Proj_Prof',
200200: 'Proj_Proj',
200500: 'Proj$Management',
200600: 'Proj$Workload',
200700: 'Proj$Recurring',
200800: 'ProjectTeam',
200900: 'ProjectAllocation',
200910: 'ProjectARRHistory',
201000: 'Campaign',
201010: 'Camp_User',
201100: 'Camp_Comp',
201101: 'Camp_Cont',
201110: 'Camp_Lead',
201150: 'Camp_Prod',
201154: 'Camp_Prof',
201200: 'Camp_Proj',
201201: 'Camp_Camp',
201500: 'Camp$Emailing',
201600: 'Camp$Training',
201700: 'CampaignPlan',
201710: 'CampaignEvents',
202000: 'Opportunity',
202010: 'Oppo_User',
202072: 'Oppo_Addr',
202100: 'Oppo_Comp',
202101: 'Oppo_Cont',
202102: 'Oppo_Lead',
202150: 'Oppo_Prod',
202151: 'Oppo_Kbas',
202154: 'Oppo_Prof',
202200: 'Oppo_Proj',
202201: 'Oppo_Camp',
202202: 'Oppo_Oppo',
203000: 'Request',
203010: 'Rqst_User',
203072: 'Rqst_Addr',
203100: 'Rqst_Comp',
203101: 'Rqst_Cont',
203102: 'Rqst_Lead',
203150: 'Rqst_Prod',
203151: 'Rqst_Kbas',
203200: 'Rqst_Proj',
203202: 'Rqst_Oppo',
203203: 'Rqst_Rqst',
205000: 'Folder',
205010: 'Fldr_User',
205100: 'Fldr_Comp',
205101: 'Fldr_Cont',
205150: 'Fldr_Prod',
205151: 'Fldr_Kbas',
205200: 'Fldr_Proj',
205201: 'Fldr_Camp',
205202: 'Fldr_Oppo',
205203: 'Fldr_Rqst',
205411: 'Fldr_Widg',
205600: 'Fldr_Layt',
206000: 'MarketingEvent',
206072: 'Mevt_Addr',
300000: 'Document',
300010: 'Docu_User',
300072: 'Docu_Addr',
300083: 'Docu_Vatr',
300100: 'Docu_Comp',
300101: 'Docu_Cont',
300150: 'Docu_Prod',
300151: 'Docu_Kbas',
300200: 'Docu_Proj',
300201: 'Docu_Camp',
300202: 'Docu_Oppo',
300203: 'Docu_Rqst',
300205: 'Docu_Fldr',
300300: 'Docu_Docu',
300421: 'Docu_Dprt',
300423: 'Docu_Cobu',
300500: 'Docu$Invoicing',
301000: 'Interaction',
301010: 'Intr_User',
301072: 'Intr_Addr',
301100: 'Intr_Comp',
301101: 'Intr_Cont',
301102: 'Intr_Lead',
301150: 'Intr_Prod',
301154: 'Intr_Prof',
301200: 'Intr_Proj',
301201: 'Intr_Camp',
301202: 'Intr_Oppo',
301203: 'Intr_Rqst',
301205: 'Intr_Fldr',
301300: 'Intr_Docu',
301301: 'Intr_Intr',
302000: 'Task',
303000: 'Mail',
304000: 'Timesheet',
305000: 'Appointment',
306000: 'Chat',
307000: 'Sms',
308000: 'Signature',
309000: 'Conversation',
310000: 'WorkingObject',
310010: 'Work_User',
310202: 'Work_Oppo',
310203: 'Work_Rqst',
310300: 'Work_Docu',
310301: 'Work_Intr',
310310: 'Work_Work',
311000: 'Timeline',
311500: 'TimelineObject',
311600: 'TimelineUser',
311700: 'TimelineSubscription',
312000: 'Note',
400000: 'Query',
400010: 'Quer_User',
400500: 'Snapshot',
401000: 'Report',
401010: 'Repo_User',
401011: 'Repo_Subs',
401401: 'Repo_Repo',
402000: 'Template',
402010: 'Tmpl_User',
402100: 'Tmpl_Comp',
402101: 'Tmpl_Cont',
402200: 'Tmpl_Proj',
402202: 'Tmpl_Oppo',
402205: 'Tmpl_Fldr',
402300: 'Tmpl_Docu',
403000: 'QueryView',
403010: 'Qrvw_User',
404000: 'Population',
404010: 'Popu_User',
404500: 'PopulationEntry',
405000: 'Rule',
406000: 'Challenge',
406010: 'Chal_User',
406156: 'Chal_Metr',
406157: 'Chal_Trpy',
406500: 'ChallengePeriod',
406600: 'ChallengeResult',
406700: 'GameMetricTarget',
406800: 'GameMetricResult',
406850: 'GameScore',
406900: 'GameEvent',
407000: 'Quiz',
407010: 'Quiz_User',
407152: 'Quiz_Qqst',
407406: 'Quiz_Chal',
407500: 'QuizResult',
407600: 'QuizQuestionResult',
408000: 'Fragment',
408003: 'Frag_Field',
408010: 'Frag_User',
408402: 'Frag_Tmpl',
409000: 'Objective',
409010: 'Objv_User',
409100: 'Objv_Comp',
409101: 'Objv_Cont',
409430: 'Objv_Objp',
410000: 'Payment',
410300: 'Paym_Docu',
411000: 'Widget',
411010: 'Widg_User',
412000: 'Credential',
412010: 'Cred_User',
412100: 'Cred_Comp',
412101: 'Cred_Cont',
412200: 'Cred_Proj',
413000: 'InvoiceBatch',
413010: 'Invb_User',
413100: 'Invb_Comp',
413200: 'Invb_Proj',
413202: 'Invb_Oppo',
413300: 'Invb_Docu',
414000: 'ExcelReport',
414500: 'ExcelReportSource',
414600: 'ExcelReportParameter',
414700: 'ExcelReportExecution',
414800: 'ExcelReportSchedule',
414900: 'ExcelDataColumn',
415000: 'Lifecycle',
415500: 'LifecycleAction',
415600: 'LifecycleRegister',
416000: 'Process',
416500: 'ProcessInstance',
416600: 'ProcessTask',
417000: 'ExtranetApp',
417101: 'Extr_Cont',
417200: 'ExtranetModule',
418000: 'DataQuality',
418101: 'Daqu_Cont',
419000: 'Pricing',
419082: 'Prcg_Crcy',
419100: 'Prcg_Comp',
419150: 'Prcg_Prod',
419151: 'Prcg_Prdf',
419404: 'Prcg_Popu',
419500: 'PricingFamily',
419600: 'PackingOption',
420000: 'Sla',
420210: 'Tmpl_Camp',
420500: 'SlaAlarm',
420600: 'SlaState',
421000: 'DataPrivacyRequest',
422000: 'SalesInvestment',
423000: 'ConfigBundle',
424000: 'RecurrencePlan',
427000: 'FiscalYear',
427100: 'Fiye_Comp',
428000: 'TransformationProfile',
428201: 'Tpro_Camp',
429000: 'Forecast',
429002: 'Forecast_Mval',
429010: 'Fore_User',
429500: 'ForecastQuota',
429600: 'ForecastFormula',
430000: 'ObjectivePeriod',
431000: 'VatCategory',
432000: 'ExtranetAdministrateCompany',
433000: 'ConfigBundleOperation',
434000: 'PopulationDependency',
435000: 'AIPrompt',
500000: 'Devl',
500010: 'Devl_User',
500100: 'Devl_Comp',
500110: 'Devl_Cont',
500200: 'Devl_Proj',
500210: 'Devl_Camp',
500220: 'Devl_Oppo',
500230: 'Devl_Rqst',
500240: 'Devl_Prof',
500300: 'Devl_Prod',
500310: 'Devl_Docu',
500320: 'Devl_Intr',
500500: 'Devl_Devl',
604170: 'ExternalLink',
800580: 'Comp$Leverancier',
800640: 'Proj$Hoofdprojec',
800650: 'Prod$Product',
800720: 'Proj$Development',
800790: 'Rqst$Origin',
800830: 'Proj$Recurringbus',
800840: 'Prod$Recurringbus',
800860: 'Comp$Asp',
801110: 'Devl$Business',
801130: 'Comp$Partners',
801220: 'Prof$Contprofile',
801230: 'Rqst$Suggestion',
801380: 'Comp$Bankinfo',
801410: 'Rqst$Knowledgebas',
801420: 'Comp$Addrpost',
801430: 'Comp$Email',
801480: 'Proj$Cloud',
801510: 'Docu$Reusablecust',
801530: 'Rqst$Rnd',
801620: 'Oppo$Lead',
801650: 'Comp$Peakmeup',
801700: 'Comp$Ftp',
801710: 'Comp$Testenv',
801750: 'Camp$Internaleven',
801770: 'Cont$Certificatio',
801920: 'Comp$Creditsafe',
801950: 'Proj$Licenceorder',
801960: 'Oppo$Evaluation',
802050: 'Docu$Certificatio',
802120: 'Proj$Hr',
802140: 'Task$Lead',
802220: 'Task$Trainingrequ',
901000: 'Cont$Candidate',
901030: 'Camp$Vacancy',
910030: 'Task$Nps',
910060: 'Comp$Uat',
910070: 'Proj$Car',
910210: 'Proj$Hardware',
912260: 'Proj$Environment',
912280: 'Docu$Proposal',
912290: 'Proj$Hr_Approve',
14092290: 'Proj$Hr_Private',
140912270: 'Proj$Contrmgmt',
140912280: 'Proj$Proposal',
140912300: 'Proj$Skills',
140912360: 'Proj$Apsis_One_Da',
140912370: 'Proj$WebCRM',
140912390: 'Comp$Ma',
140912420: 'Docu$Ma',
140912430: 'Proj$Ma',
140912510: 'Custom_Widget_Churn',
140912520: 'Widget_Sales_Tables'
};
/**
* Decodes a Base62 encoded string to a number. Based65 is used for Maxo keys
*/
function base62Decode(recordKey) {
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const base = alphabet.length;
let decoded = 0;
for (let i = 0; i < recordKey.length; i++) {
const char = recordKey[i];
const index = alphabet.indexOf(char);
if (index === -1)
return null;
decoded = decoded * base + index;
}
return decoded;
}
/**
* Parses a Record Key (16 characters) into its components: license code, table key, and next key
* The table name is derived from the table key using a predefined mapping.
* Warning, newer and custmon tables are not included in the mapping.
*/
function parseRecordKey(recordKey) {
// Validate input
if (!recordKey || recordKey.length !== 16) {
return null;
}
const licenseCode = base62Decode(recordKey.substring(0, 4));
const stblKTable = base62Decode(recordKey.substring(4, 8));
const stblName = stblKTable ? getTableNameById(stblKTable) : null;
const nextKey = base62Decode(recordKey.substring(8, 16));
return {
licenseCode,
stblKTable,
stblName,
nextKey,
};
}
// Type-safe function to get the table name by ID
function getTableNameById(id) {
return SysTableIdList[id] ?? null;
}
var index$2 = /*#__PURE__*/Object.freeze({
__proto__: null,
base62Decode: base62Decode,
getTableNameById: getTableNameById,
parseRecordKey: parseRecordKey
});
// Base interceptor class with generic type for interceptor function
// eslint-disable-next-line @typescript-eslint/ban-types
class CrmFetchInterceptor {
funcs = [];
/**
* Generic use method to add interceptors
* @example
* crmEnv.interceptors.onRequest.use(async(request: Request) => {
* onRequestUrlOrigin = new URL(request.url).origin;
* })
* crmEnv.interceptors.onPositiveResponse.use(async(response: Response) => {
* onResponseCustomHeader = response.headers.get("x-efficy-status") ?? "";
* })
* crmEnv.interceptors.onError.use(async(e: Error, request: Request, requestPayload: ModulePostPayload | undefined, response: Response | null) => {
* if (requestPayload && typeof requestPayload === "object") requestObject = requestPayload;
* })
*/
use(func) {
this.funcs.push(func);
}
// Generic clear method to remove all interceptors
clear() {
this.funcs.length = 0;
}
// Generic get interceptors method
get interceptors() {
return [...this.funcs]; // Create a copy to prevent mutation
}
// Internal method to chain interceptors for request or response handling
async chain(...args) {
for (const func of this.funcs) {
await func(...args);
}
}
}
class CrmFetchRequestInterceptor extends CrmFetchInterceptor {
async handle(request) {
await this.chain(request);
}
}
class CrmFetchResponseInterceptor extends CrmFetchInterceptor {
async handle(response) {
await this.chain(response);
}
}
class CrmFetchErrorInterceptor extends CrmFetchInterceptor {
async handle(e, request, requestPayload, response) {
await this.chain(e, request, requestPayload, response);
}
}
/**
* Defines the connection properties for your Efficy web API
*/
class CrmEnv {
env;
name = "CrmEnv";
id;
url;
customer;
apiKey;
user;
pwd;
cookies;
logOff;
useFetchQueue;
retryWithNewSession;
useCookies;
/**
* Creates a CrmEnv instance.
* @param env The definition object of the targeted CRM environment.
*/
constructor(env) {
this.env = env;
if (typeof env === "object") {
this.setEnv(env);
}
else {
this.setEnv({
url: window.location.origin
});
}
}
/**
* Updates the CRM environment configuration.
* @param env The definition object of the targeted CRM environment.
*/
setEnv(env) {
this.id = env.id;
this.url = env.url || "";
this.customer = env.customer || "";
// Sensitive properties
this.apiKey = env.apiKey || "";
this.user = env.user || "";
this.pwd = env.pwd || "";
this.cookies = Array.isArray(env.cookies) ? env.cookies : [];
// Booleans
this.logOff = typeof env.logOff === "boolean" ? env.logOff : false;
this.useFetchQueue = typeof env.useFetchQueue === "boolean" ? env.useFetchQueue : false;
this.retryWithNewSession = typeof env.retryWithNewSession === "boolean" ? env.retryWithNewSession : false;
this.useCookies = typeof env.useCookies === "boolean" ? env.useCookies : true;
// Remove trailing slash from url
this.url = this.url.replace(/\/$/, "");
}
clearCookies() {
this.cookies = [];
}
/**
* Returns the request header for cookies.
*/
get cookieHeader() {
if (!Array.isArray(this.cookies))
return "";
const validCookies = this.cookies.filter(cookie => new Date() < new Date(cookie.expires ?? ""));
const header = validCookies.map(cookie => `${cookie.name}=${cookie.value}`).join("; ");
return header;
}
/**
* Returns the session ID from cookies.
*/
get sessionId() {
return Array.isArray(this.cookies) && this.cookies.length > 0 ? this.cookies[0].value ?? "" : "";
}
/**
* Returns the first part of the session ID.
*/
get shortSessionId() {
const str = this.sessionId;
return str.split("-")[0] || "";
}
interceptors = Object.freeze({
onRequest: new CrmFetchRequestInterceptor(),
onPositiveResponse: new CrmFetchResponseInterceptor(),
onError: new CrmFetchErrorInterceptor(),
});
}
class CrmFetch {
crmEnv;
subpage;
isPublicAPI;
name = "CrmFetch";
sessionId;
requestCounter = 0;
_lastResponseObject = null;
_lastResponseStatus = 0;
constructor(crmEnv = new CrmEnv(), subpage = "crm/", isPublicAPI = false) {
this.crmEnv = crmEnv;
this.subpage = subpage;
this.isPublicAPI = isPublicAPI;
this.setFetchOptions();
}
fetchOptions = {
method: "POST",
headers: {
'Accept': 'application/json'
},
credentials: undefined
};
get lastResponseObject() {
return this._lastResponseObject;
}
get lastResponseStatus() {
return this._lastResponseStatus;
}
setFetchOptions() {
const headers = { ...this.fetchOptions.headers };
if (this.isPublicAPI && this.crmEnv.apiKey) {
headers["X-Api-Key"] = this.crmEnv.apiKey;
}
else {
if (this.crmEnv.apiKey) {
headers["X-Efficy-ApiKey"] = this.crmEnv.apiKey;
}
else if (this.crmEnv.user && this.crmEnv.pwd) {
headers["X-Efficy-User"] = this.crmEnv.user;
headers["X-Efficy-Pwd"] = this.crmEnv.pwd;
}
if (this.crmEnv.logOff) {
headers["X-Efficy-Logoff"] = true;
}
if (this.crmEnv.useCookies) {
this.fetchOptions.credentials = "include";
}
}
if (this.crmEnv.useFetchQueue) {
FetchQueue.forceSequential = true;
}
this.fetchOptions.headers = headers;
}
initJsonFetch(method) {
const requestHeaders = new Headers(this.fetchOptions.headers);
requestHeaders.set('Content-Type', 'application/json');
this.fetchOptions.headers = requestHeaders;
this.fetchOptions.method = method;
}
async crmfetch(requestUrl, requestPayload, requestOptions, isRetry = false) {
let response = null;
let responseBody = "";
let responseObject = null;
let responseStatusCode = 0;
const init = {};
Object.assign(init, this.fetchOptions, requestOptions);
if (this.crmEnv.useCookies && this.crmEnv.cookieHeader) {
init.headers = {
"Cookie": this.crmEnv.cookieHeader
};
}
const request = new Request(requestUrl, init);
await this.crmEnv.interceptors.onRequest.handle(request);
const fetchQueue = new FetchQueue();
try {
if (this.crmEnv.useFetchQueue)
await fetchQueue.waitMyTurn();
response = await globalThis.fetch(request);
responseStatusCode = response.status;
responseBody = await response.text();
try {
if (responseBody) {
responseObject = JSON.parse(responseBody);
this._lastResponseObject = responseObject;
this._lastResponseStatus = responseStatusCode;
}
}
catch (e) {
// Ignore JSON parsing errors
}
if (!response.ok) {
if (response.status === 404) {
// Not Found - The requested resource doesn’t exist.
responseObject = null;
}
else if (response.status !== 401) {
throw new CrmFetchError(response.status, responseBody, requestUrl);
}
}
}
catch (e) {
await this.crmEnv.interceptors.onError.handle(e, request, requestPayload, response);
throw e;
}
finally {
if (this.crmEnv.useFetchQueue)
fetchQueue.finished();
}
const crmException = this.getCrmException(responseObject);
// Capture returned cookies from the response, even if this.crmEnv.useCookies is false.
const cookieString = response.headers.get('set-cookie');
if (cookieString) {
const cookie = parseEfficyCookieString(cookieString);
this.crmEnv.cookies = [cookie];
this.sessionId = this.crmEnv.shortSessionId;
}
// The long list of possible errors that could be caused by an expired session.
const couldBeExpiredSession = (responseStatusCode === 401
|| crmException?.detail === "FRMK-2612" // Efficy U 1.0
|| crmException?.detail === "CORE-1243"
|| crmException?.message.includes("This operation requires a Database Connection")
|| crmException?.message.includes("You aren't authorized to execute this query")
|| crmException?.message.includes("Invalid User")
|| crmException?.message.includes("You do not have the right to perform this operation")
|| crmException?.message.includes("Session timeout") // Efficy U 1.0
|| crmException?.message.includes("RetrieveUsersList: Please Logon to Database") // Enterprise only
|| crmException?.message.includes("The CRM Session is not active, please log in first") // Maxo 2.0
);
if (couldBeExpiredSession && this.crmEnv.retryWithNewSession && isRetry === false) {
this.crmEnv.clearCookies();
return this.crmfetch(requestUrl, requestPayload, requestOptions, true);
}
if (crmException) {
const e = crmException.error;
await this.crmEnv.interceptors.onError.handle(e, request, requestPayload, response);
throw e;
}
if (response) {
await this.crmEnv.interceptors.onPositiveResponse.handle(response);
}
return responseObject;
}
getRequestUrl(crmPath, queryArgs) {
const searchParams = new URLSearchParams();
if (queryArgs) {
for (const [key, value] of Object.entries(queryArgs)) {
searchParams.append(key, value.toString());
}
}
// Useful for development environments
if (this.crmEnv.customer) {
searchParams.append("customer", this.crmEnv.customer);
}
const queryString = searchParams.toString();
const requestUrl = `${this.crmEnv.url}/${this.subpage}${crmPath}?${queryString}`;
return requestUrl;
}
/**
* Parse errors in both legacy Enterprise format as from the U {data, errors, status} format
* @param responseObject
*/
getCrmException(responseObject) {
if (!responseObject)
return undefined;
const resp = responseObject;
if (Array.isArray(resp)) {
const errWrapper = resp.find(operation => operation["@name"] === "exception");
if (errWrapper && errWrapper["#error"]) {
const err = errWrapper["#error"];
return new CrmException(err.message ?? err.errorstring, err.code ?? err.errorcode, err.detail);
}
}
else if (Array.isArray(resp.errors) && resp.errors.length > 0) {
const [err] = resp.errors;
return new CrmException(err.detail, err.id, err.exta);
}
else if (typeof resp === "object" && typeof resp["#error"] === "object") {
const err = resp["#error"];
return new CrmException(err.message ?? err.errorstring, err.code ?? err.errorcode, err.detail);
}
else if (typeof resp === "object" && resp["error"] === true) {
const err = resp;
return new CrmException(err.message ?? err.errorstring, err.code ?? err.errorcode, err.detail);
}
else if (isJsonApiResponse(resp) && Array.isArray(resp["errors"]) && resp["errors"].length > 0 && isJsonApiErrorNode(resp["errors"][0])) {
const [err] = resp["errors"];
return CrmException.fromJsonApiErrorNode(err);
}
}
}
class CrmFetchError extends Error {
statusCode;
url;
static appId = "efficy-u-rpc-api";
bodyText;
bodyObject;
constructor(statusCode, body, url) {
super([CrmFetchError.appId, statusCode].join(' ').trim());
this.statusCode = statusCode;
this.url = url;
this.bodyText = body;
try {
this.bodyObject = JSON.parse(body);
}
catch {
// ignore
}
}
}
/**
* Technical helper for forcing zero concurrency in fetch execution of CrmRpc and CrmApi.
* @deprecated
*/
class FetchQueue {
#id = 0;
#startTime = 0;
static forceSequential = false;
static waitTime = 10; // milliseconds
static pending = false;
static fetchCount = 0;
static totalRequestTime = 0;
static minRequestTime = Infinity;
static maxRequestTime = 0;
constructor() {
FetchQueue.fetchCount++;
this.#id = FetchQueue.fetchCount;
}
pleaseWait() {
return (FetchQueue.pending === true);
}
takeTurn() {
FetchQueue.pending = true;
this.#startTime = Date.now();
}
finished() {
const requestTime = Date.now() - this.#startTime;
FetchQueue.totalRequestTime += requestTime;
FetchQueue.minRequestTime = Math.min(FetchQueue.minRequestTime, requestTime);
FetchQueue.maxRequestTime = Math.max(FetchQueue.maxRequestTime, requestTime);
FetchQueue.pending = false;
}
async sleep() {
await new Promise(r => setTimeout(r, FetchQueue.waitTime));
}
async waitMyTurn() {
if (FetchQueue.forceSequential) {
while (this.pleaseWait()) {
await this.sleep();
}
}
this.takeTurn();
}
static get averageRequestTime() {
return FetchQueue.totalRequestTime / FetchQueue.fetchCount;
}
static get stats() {
return {
"fetchCount": FetchQueue.fetchCount,
"averageRequestTime": FetchQueue.averageRequestTime,
"totalRequestTime": FetchQueue.totalRequestTime,
"maxRequestTime": FetchQueue.maxRequestTime,
"minRequestTime": FetchQueue.minRequestTime
};
}
}
class CrmException {
message;
code;
detail;
constructor(message, code = "RPC", detail = "") {
this.message = message;
this.code = code;
this.detail = detail;
}
toString() {
return [this.code, this.message, this.detail].join(" - ");
}
static fromJsonApiErrorNode(o) {
return new CrmException(o.title, o.id, o.detail);
}
get error() {
return new Error(this.toString());
}
}
function parseEfficyCookieString(cookieStr) {
const keyValuePairs = cookieStr.split(';');
const name = "EfficySession";
let value = "";
let path = "";
let expires = "";
for (const pair of keyValuePairs) {
const [key, _value] = pair.trim().split('=');
if (key === "EfficySession") {
value = decodeURIComponent(_value);
}
if (key === "path") {
path = decodeURIComponent(_value);
}
if (key === "expires") {
expires = decodeURIComponent(_value);
}
}
return {
name,
value,
path,
expires
};
}
/**
* Efficy SDK build for requesting custom Efficy nodes (aka Webservices) on the endpoint "crm/node"
*/
class CrmNode extends CrmFetch {
crmEnv;
constructor(crmEnv = new CrmEnv()) {
super(crmEnv);
this.crmEnv = crmEnv;
this.name = "CrmNode";
}
/**
*
* @param nodePath The node path without the "/crm/node/"" prefix, e.g. "echo"
* @param payload
* @param queryStringArgs
* @example
* const payload = {msg: "Hello, this is a unit test!"};
* const result = await crm.crmNodeData<EchoResponse>("echo", payload);
*/
async crmNodeData(nodePath, payload, queryStringArgs) {
const response = await this.crmNode(nodePath, payload, queryStringArgs);
if (isJsonApiResponse(response)) {
return response.data;
}
else {
throw new Error(`${this.name}.crmNode::unexpected response`);
}
}
/**
*
* @param nodePath The node path without the "/crm/node/"" prefix, e.g. "echo"
* @param payload
* @param queryStringArgs
* @example
* const payload = {msg: "Hello, this is a unit test!"};
* const result = await crm.crmNode("echo", payload)?.data;
*/
async crmNode(nodePath, payload, queryStringArgs) {
const requestUrl = this.getRequestUrl("node/" + nodePath, queryStringArgs);
const requestOptions = {};
if (payload) {
if (payload instanceof FormData) {
requestOptions.body = payload;
}
else if (typeof payload === "object") {
this.initJsonFetch("POST");
requestOptions.body = JSON.stringify(payload);
}
else if (typeof payload === 'object' && payload instanceof URLSearchParams) {
const p = payload;
requestOptions.body = p.toString();
}
else {
throw new Error(`${this.name}.crmNode::Unsupported payload type`);
}
}
else {
this.fetchOptions.method = "GET";
}
return await this.crmfetch(requestUrl, payload, requestOptions);
}
}
var searchGlobalService = async (crmApi, payload) => {
if (payload.search.entities.length > 0) {
if (payload.searchMine) {
payload.search.refinedOptions.onlyMyItems = true;
}
const queryStringArgs = transformAsQueryStringArgs(payload.search);
return await crmApi.crmGetData("search-global/query", queryStringArgs);
}
return null;
};
const transformAsQueryStringArgs = (search) => ({
entities: JSON.stringify(search.entities.filter((entity) => entity)),
offset: search.offset.toString(),
quantity: search.quantity.toString(),
query: search.value,
refinedOptions: JSON.stringify(search.refinedOptions),
});
/**
* Find and return the first (deep) nested object where all properties of the provided searchObject match.
* Inspired by https://pretagteam.com/question/finding-an-object-deep-in-a-nested-json-object
*/
function findDeep(object, searchObject) {
if (typeof searchObject !== 'object' || searchObject === null) {
throw new Error('findDeep::searchObject must be a non-null object');
}
const isEqual = (obj) => {
for (const key in searchObject) {
if (!Object.prototype.hasOwnProperty.call(obj, key) || obj[key] !== searchObject[key]) {
return false;
}
}
return true;
};
if (Array.isArray(object)) {
for (const item of object) {
const result = findDeep(item, searchObject);
if (result)
return result;
}
}
else if (typeof object === 'object' && object !== null) {
if (isEqual(object)) {
return object;
}
for (const key of Object.keys(object)) {
const nestedObject = findDeep(object[key], searchObject);
if (nestedObject)
return nestedObject;
}
}
}
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
/**
* Low level class representing an RPC operation
*/
class RpcObject {
remoteAPI;
requestObject;
id;
responseObject;
constructor(remoteAPI) {
this.remoteAPI = remoteAPI;
this.remoteAPI = remoteAPI;
this.id = uuidv4();
this.requestObject = null;
this.responseObject = {};
}
get api() {
return this.remoteAPI;
}
afterExecute() {
}
asJsonRpc() {
return null;
}
registerObject(object) {
this.remoteAPI.remoteObjects.push(object);
}
findDataSetArray(resp, dataSetName = "dataset") {
let list = [];
if (typeof resp !== "object")
return list;
const result = findDeep(resp, { "#class": dataSetName });
if (!result || typeof result["#data"] !== "object" || result["#data"] === null)
return list;
if (Array.isArray(result["#data"])) {
list = result["#data"] ?? [];
}
else if (Array.isArray(result["#data"]["data"])) {
list = result["#data"]["data"] ?? [];
}
return list;
}
findListArray(resp, listName = "stringlist") {
return this.findDataSetArray(resp, listName) ?? [];
}
findAttachment(resp, key) {
if (typeof resp !== "object")
return;
return findDeep(resp, { "@name": "attachment", "key": key });
}
findFunc(resp, name) {
if (typeof resp !== "object" || !Array.isArray(resp["@func"]))
return;
return resp["@func"].find(item => item["@name"] === name);
}
findFunc2(resp, name, name2, value2) {
if (typeof resp !== "object" || !Array.isArray(resp["@func"]))
return;
return resp["@func"].find(item => item["@name"] === name && item[name2] === value2);
}
findFuncArray(resp, name) {
const result = this.findDataSetArray(this.findFunc(resp, name));
return Array.isArray(result) ? result : null;
}
findFuncArray2(resp, name, name2, value2) {
const result = this.findDataSetArray(this.findFunc2(resp, name, name2, value2));
return Array.isArray(result) ? result : null;
}
}
/**
* Class uses by operations that return a string result
*/
class StringObject extends RpcObject {
operationName = "";
result = "";
constructor(remoteAPI) {
super(remoteAPI);
this.registerObject(this);
}
afterExecute() {
super.afterExecute();
const output = this.findFunc(this.responseObject, this.operationName)?.["#result"];
if (output && typeof output === "string") {
this.result = output;
}
}
}
/**
* Class returned by API property operation such as currentdatabasealias, currentuserfullname
*/
class PropertyObject extends StringObject {
name;
constructor(remoteAPI, name) {
super(remoteAPI);
this.name = name;
this.operationName = name;
this.registerObject(this);
}
asJsonRpc() {
const api = {
"@name": this.operationName
};
const requestObject = this.requestObject = {
"#id": this.id,
"@name": "api",
"@func": [api]
};
return requestObject;
}
}
/**
* Class returned by getSetting operation
*/
class SettingObject extends StringObject {
module;
name;
asString;
operationName = "getsetting";
/**
* @param remoteAPI
* @param module The name of the module (JSON object) that owns the setting.
* @param name The name of the setting.
* @param asString If true, the settings of type TDateTime will be returned as a string formatted with the ShortDateTime format. If false, it will be returned as a float value.
*/
constructor(remoteAPI, module, name, asString = true) {
super(remoteAPI);
this.module = module;
this.name = name;
this.asString = asString;
this.registerObject(this);
}
/** @protected */
asJsonRpc() {
const api = {
"@name": this.operationName,
"module": this.module,
"name": this.name,
"asstring": this.asString
};
const requestObject = this.requestObject = {
"#id": this.id,
"@name": "api",
"@func": [api]
};
return requestObject;
}
}
class DataSetInternal {
type;
name;
filter;
includeBlobContent;
tableView = 0;
_items = [];
_item = null;
constructor(type, name, filter, includeBlobContent) {
this.type = type;
this.name = name;
this.filter = filter;
this.includeBlobContent = includeBlobContent;
this.type = type;
this.name = name;
this.filter = filter && typeof filter == "string" ? filter : undefined;
this.includeBlobContent = typeof includeBlobContent === "boolean" ? includeBlobContent : false;
}
/**
* The to array converted dataset
*/
get items() {
return this._items;
}
/**
* When exists, the first item of the items array, else null
*/
get item() {
return this._item;
}
setItems(value) {
if (!value)
return;
this._items = value;
if (this._items.length > 0) {
this._item = this._items[0];
}
}
get func() {
const func = {};
func["@name"] = this.type;
if (this.name)
func[this.type] = this.name;
if (this.filter)
func["filter"] = this.filter;
if (this.tableView > 0)
func["tableview"] = this.tableView;
if (this.includeBlobContent)
func["includeblobcontent"] = true;
return func;
}
get remoteDataSet() {
return new DataSet(this);
}
}
class DataSet {
ds;
constructor(ds) {
this.ds = ds;
}
get name() {
return this.ds?.name;
}
get type() {
return this.ds?.type;
}
get filter() {
return this.ds?.filter;
}
get includeBlobContent() {
return this.ds?.includeBlobContent;
}
/**
* The to array converted dataset
*/
get items() {
return this.ds ? this.ds.items : [];
}
/**
* When exists, the first item of the items, else null
*/
get item() {
return this.ds ? this.ds.item : null;
}
}
class RemoteDataSet extends RpcObject {
ds = null;
constructor(remoteAPI) {
super(remoteAPI);
this.registerObject(this);
}
dataSetName;
afterExecute() {
super.afterExecute();
this.ds = new DataSetInternal("main", "main");
this.ds.setItems(this.findDataSetArray(this.responseObject, this.dataSetName));
}
/**
* The to array converted dataset
*/
get items() {
return this.ds ? this.ds.items : [];
}
/**
* When exists, the first item of the items, else null
*/
get item() {
return this.ds ? this.ds.item : null;
}
}
class DataSetTableView {
category = [];
detail = [];
}
/**
* Groups a list of DataSet operations that are shared between ConsultObject and EditObject
*/
class DataSetList extends RpcObject {
master = null;
master1 = null;
tableView = new DataSetTableView();
constructor(remoteAPI) {
super(remoteAPI);
this.resetState();
}
getMasterDataSet(masterView = 0) {
if (masterView > 0) {
this.master1 = new DataSetInternal("master", "master", undefined, undefined);
this.master1.tableView = 1;
return this.master1;
}
else {
this.master = new DataSetInternal("master", "master", undefined, undefined);
return this.master;
}
}
getCategoryDataSet(categoryName) {
const ds = new DataSetInternal("category", categoryName);
this.tableView.category.push(ds);
return ds;
}
getDetailDataSet(detail, filter = "", includeBlobContent = false) {
const ds = new DataSetInternal("detail", detail, filter, includeBlobContent);
this.tableView.detail.push(ds);
return ds;
}
resetState() {
this.master = null;
this.master1 = null;
this.tableView = new DataSetTableView();
}
get funcs() {
const array = [];
this.master && array.push(this.master.func);
this.master1 && array.push(this.master1.func);
[...this.tableView.category, ...this.tableView.detail].forEach(ds => {
array.push(ds.func);
});
return array;
}
afterExecute() {
this.master && this.setDsoItems(this.master);
this.master1 && this.setDsoItems(this.master1);
[...this.tableView.category, ...this.tableView.detail].forEach(ds => {
this.setDsoItems(ds);
});
}
setResponseObject(value) {
this.responseObject = value;
}
/**
* Add the remotely fetched master, categories and detail data as properties of data
*/
setData(target) {
target.data = {};
target.data.master = this.master?.item;
target.data.master1 = this.master1?.item;
}
setDsoItems(dso) {
if (dso.tableView > 0) {
const item = this.findFuncArray2(this.responseObject, dso.type, "tableview", dso.tableView);
if (item)
dso.setItems(item);
}
else {
const item = this.findFuncArray2(this.responseObject, dso.type, dso.type, dso.name);
if (item)
dso.setItems(item);
}
}
}
/**
* Class returned by getUserList operation
*/
class UserList extends RemoteDataSet {
constructor(remoteAPI) {
super(remoteAPI);
}
asJsonRpc() {
const requestObject = this.requestObject = {
"#id": this.id,
"@name": "api",
"@func": [{ "@name": "userlist" }]
};
return requestObject;
}
}
/**
* Class returned by consultRecent operations
*/
class RecentList extends RemoteDataSet {
entity;
extraFields;
constructor(remoteAPI, entity = "", extraFields = []) {
super(remoteAPI);
this.entity = entity;
this.extraFields = extraFields;
}
asJsonRpc() {
const api = {
"@name": "recentlistex",
"extrafields": this.extraFields.join(","),
};
if (this.entity)
api.entity = this.entity;
const requestObject = this.requestObject = {
"#id": this.id,
"@name": "api",
"@func": [api]
};
return requestObject;
}
}
/**
* Class returned by consultFavorites operations
*/
class FavoriteList extends RemoteDataSet {
entity;
constructor(remoteAPI, entity) {
super(remoteAPI);
this.entity = entity;
}
asJsonRpc() {
const api = {
"@name": "favoritelist"
};
if (this.entity)
api.entity = this.entity;
const requestObject = this.requestObject = {
"#id": this.id,
"@name": "api",
"@func": [api]
};
return requestObject;
}
}
/**
* Class returned by searchContactsByEmail, searchContactsByPhone operations
*/
class ContactsList extends RemoteDataSet {
recipients;
phoneNumber;
/**
* @param remoteAPI
* @param recipients The list of email addresses
* @param phoneNumber The phone number, doesn't have to be stripped from formatting
*/
constructor(remoteAPI, recipients = [], phoneNumber = "") {
super(remoteAPI);
this.recipients = recipients;
this.phoneNumber = phoneNumber;
}
asJsonRpc() {
let api;
if (Array.isArray(this.recipients) && this.recipients.length > 0) {
api = {
"@name": "contactidsfromemailaddresses",
"recipients": this.recipients.join(";")
};
}
else if (this.phoneNumber) {
api = {
"@name": "searchcontactsbyphone",
"phonenumber": this.phoneNumber
};
}
if (!api)
throw Error("ContactsList.asJsonRpc::unable to define the operation @name");
const requestObject = this.requestObject = {
"#id": this.id,
"@name": "api",
"@func": [api]
};
return requestObject;
}
}
/**
* Class returned by consultManyEx operations
*/
class ConsultManyObject extends RemoteDataSet {
entity;
whereFields;
whereValues;
orderByExpression;
constructor(remoteAPI, entity, whereFields = [], whereValues = [], orderByExpression = "") {
super(remoteAPI);
this.entity = entity;
this.whereFields = whereFields;
this.whereValues = whereValues;
this.orderByExpression = orderByExpression;
}
asJsonRpc() {
const api = {
"@name": "consultmanyex",
"entity": this.entity,
"findfield": this.whereFields.join(";"),
"keys": this.whereValues.join(";"),
"orderbyfield": this.orderByExpression,
"separator": ";"
};
const requestObject = this.requestObject = {
"#id": this.id,
"@name": "api",
"@func": [api]
};
return requestObject;
}
}
/**
* Class returned by methods such as getCategoryCollection
*/
class CollectionObject extends RemoteDataSet {
entity;
detail;
constructor(remoteAPI, entity, detail) {
super(remoteAPI);
this.entity = entity;
this.detail = detail;
this.dataSetName = "collection";
}
asJsonRpc() {
const api = {
"@name": "getcategorycollection"
};
if (this.entity)
api.entity = this.entity;
if (this.detail)
api.detail = this.detail;
const requestObject = {
"#id": this.id,
"@name": "api",
"@func": [api]
};
return requestObject;
}
}
/**
* Class returned by openConsultObject
*/
class ConsultObject extends RpcObject {
entity;
key;
dataSetList;
isDirty = false;
/**
* Opens an consult context for the record identified by entity and key.
* @param remoteAPI
* @param entity The entity name of the consulted record, e.g. "Comp"
* @param key The key of the consulted record
*/
constructor(remoteAPI, entity, key) {
super(remoteAPI);
this.entity = entity;
this.key = key;
this.dataSetList = new DataSetList(remoteAPI);
this.resetState();
this.setDirty();
}
/**
* resetState and isDirty allows to reuse the existing class after an executeBatch
*/
resetState() {
this.dataSetList.resetState();
this.isDirty = false;
}
setDirty() {
if (this.isDirty)
return;
this.registerObject(this);
this.isDirty = true;
}
/**
* Retrieves a master DataSet from the consult context.
*/
getMasterDataSet() {
this.setDirty();
return this.dataSetList.getMasterDataSet().remoteDataSet;
}
/**
* Retrieves the DataSet for category categoryName. Can be null when