UNPKG

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
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