@rockcarver/frodo-lib 0.12.3 → 0.12.5-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -1
- package/cjs/api/AuthenticateApi.js +2 -3
- package/cjs/api/AuthenticateApi.js.map +1 -1
- package/cjs/api/BaseApi.js +2 -2
- package/cjs/api/BaseApi.js.map +1 -1
- package/cjs/ops/AuthenticateOps.js +17 -4
- package/cjs/ops/AuthenticateOps.js.map +1 -1
- package/cjs/ops/ConnectionProfileOps.js +20 -5
- package/cjs/ops/ConnectionProfileOps.js.map +1 -1
- package/cjs/ops/JourneyOps.js +167 -146
- package/cjs/ops/JourneyOps.js.map +1 -1
- package/cjs/ops/NodeOps.js +343 -0
- package/cjs/ops/NodeOps.js.map +1 -0
- package/cjs/storage/SessionStorage.js +7 -1
- package/cjs/storage/SessionStorage.js.map +1 -1
- package/esm/api/AuthenticateApi.mjs +2 -1
- package/esm/api/BaseApi.mjs +6 -2
- package/esm/ops/AuthenticateOps.mjs +15 -4
- package/esm/ops/ConnectionProfileOps.mjs +20 -5
- package/esm/ops/JourneyOps.mjs +144 -124
- package/esm/ops/NodeOps.mjs +294 -0
- package/esm/storage/SessionStorage.mjs +7 -1
- package/package.json +1 -1
- package/types/api/AuthenticateApi.d.ts.map +1 -1
- package/types/api/BaseApi.d.ts.map +1 -1
- package/types/ops/AuthenticateOps.d.ts.map +1 -1
- package/types/ops/ConnectionProfileOps.d.ts +4 -0
- package/types/ops/ConnectionProfileOps.d.ts.map +1 -1
- package/types/ops/JourneyOps.d.ts +26 -6
- package/types/ops/JourneyOps.d.ts.map +1 -1
- package/types/ops/NodeOps.d.ts +29 -0
- package/types/ops/NodeOps.d.ts.map +1 -0
- package/types/storage/SessionStorage.d.ts +7 -1
- package/types/storage/SessionStorage.d.ts.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ops/JourneyOps.ts"],"names":[],"mappings":"AAwEA,UAAU,2BAA2B;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAGD,wBAAgB,iCAAiC,IAAI,2BAA2B,CAa/E;AAED,UAAU,0BAA0B;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;CACpD;AAGD,wBAAgB,gCAAgC,IAAI,0BAA0B,CAK7E;AAqGD;;GAEG;AACH,UAAU,aAAa;IACrB;;OAEG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IACd;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAIR,GACA,OAAO,CAAC,2BAA2B,CAAC,CAsWtC;AAED;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAUtD;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;GAEG;AACH,UAAU,aAAa;IACrB;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IACd;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,2BAA2B,EACvC,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAqaf;AA6ED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,iBA6EvB;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,iBAkFvB;AAkDD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,iBAOvB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,aAAa,iBAcnE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,KAAA,MAwD1C;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAgF5D;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,OAAO,EAAE,GACvB,OAAO,CAAC,OAAO,EAAE,CAAC,CAepB;AAuND;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,OAAO,KAAA,oBAmErC;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,UAAQ,EACZ,OAAO,UAAQ,GACd,OAAO,CAAC,OAAO,EAAE,CAAC,CAyCpB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,KAAA,EACP,QAAQ,UAAO;;GAqKhB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,KAAA,eAmC3C","file":"JourneyOps.d.ts","sourcesContent":["import fs from 'fs';\nimport { v4 as uuidv4 } from 'uuid';\nimport _ from 'lodash';\nimport {\n convertBase64TextToArray,\n getTypedFilename,\n saveJsonToFile,\n getRealmString,\n convertTextArrayToBase64,\n convertTextArrayToBase64Url,\n} from './utils/ExportImportUtils';\nimport { getRealmManagedUser, replaceAll } from './utils/OpsUtils';\nimport storage from '../storage/SessionStorage';\nimport {\n getNode,\n putNode,\n deleteNode,\n getNodeTypes,\n getNodesByType,\n} from '../api/NodeApi';\nimport { getTrees, getTree, putTree, deleteTree } from '../api/TreeApi';\nimport { getEmailTemplate, putEmailTemplate } from '../api/EmailTemplateApi';\nimport { getScript } from '../api/ScriptApi';\nimport * as global from '../storage/StaticStorage';\nimport {\n printMessage,\n createProgressIndicator,\n updateProgressIndicator,\n stopProgressIndicator,\n createTable,\n} from './utils/Console';\nimport wordwrap from './utils/Wordwrap';\nimport {\n getProviderByLocationAndId,\n getProviders,\n getProviderMetadata,\n createProvider,\n findProviders,\n updateProvider,\n} from '../api/Saml2Api';\nimport {\n createCircleOfTrust,\n getCirclesOfTrust,\n updateCircleOfTrust,\n} from '../api/CirclesOfTrustApi';\nimport {\n decode,\n encode,\n encodeBase64Url,\n isBase64Encoded,\n} from '../api/utils/Base64';\nimport {\n getSocialIdentityProviders,\n putProviderByTypeAndId,\n} from '../api/SocialIdentityProvidersApi';\nimport { getThemes, putThemes } from '../api/ThemeApi';\nimport { createOrUpdateScript } from './ScriptOps';\n\nconst containerNodes = ['PageNode', 'CustomPageNode'];\n\nconst scriptedNodes = [\n 'ConfigProviderNode',\n 'ScriptedDecisionNode',\n 'ClientScriptNode',\n 'SocialProviderHandlerNode',\n 'CustomScriptNode',\n];\n\nconst emailTemplateNodes = ['EmailSuspendNode', 'EmailTemplateNode'];\n\nconst emptyScriptPlaceholder = '[Empty]';\n\ninterface SingleJourneyExportTemplate {\n meta?: Record<string, unknown>;\n innerNodes?: Record<string, unknown>;\n innernodes?: Record<string, unknown>;\n nodes?: Record<string, unknown>;\n scripts?: Record<string, unknown>;\n emailTemplates?: Record<string, unknown>;\n socialIdentityProviders?: Record<string, unknown>;\n themes?: unknown[];\n saml2Entities?: Record<string, unknown>;\n circlesOfTrust?: Record<string, unknown>;\n tree: Record<string, unknown>;\n}\n\n// use a function vs a template variable to avoid problems in loops\nexport function createSingleJourneyExportTemplate(): SingleJourneyExportTemplate {\n return {\n meta: {},\n innerNodes: {},\n nodes: {},\n scripts: {},\n emailTemplates: {},\n socialIdentityProviders: {},\n themes: [],\n saml2Entities: {},\n circlesOfTrust: {},\n tree: {},\n } as SingleJourneyExportTemplate;\n}\n\ninterface MultiJourneyExportTemplate {\n meta?: Record<string, unknown>;\n trees: Record<string, SingleJourneyExportTemplate>;\n}\n\n// use a function vs a template variable to avoid problems in loops\nexport function createMultiJourneyExportTemplate(): MultiJourneyExportTemplate {\n return {\n meta: {},\n trees: {},\n } as MultiJourneyExportTemplate;\n}\n\n/**\n * Helper to get all SAML2 dependencies for a given node object\n * @param {Object} nodeObject node object\n * @param {[Object]} allProviders array of all saml2 providers objects\n * @param {[Object]} allCirclesOfTrust array of all circle of trust objects\n * @returns {Promise} a promise that resolves to an object containing a saml2 dependencies\n */\nasync function getSaml2NodeDependencies(\n nodeObject,\n allProviders,\n allCirclesOfTrust\n) {\n const samlProperties = ['metaAlias', 'idpEntityId'];\n const saml2EntityPromises = [];\n for (const samlProperty of samlProperties) {\n // In the following line nodeObject[samlProperty] will look like '/alpha/iSPAzure'.\n const entityId =\n samlProperty === 'metaAlias'\n ? _.last(nodeObject[samlProperty].split('/'))\n : nodeObject[samlProperty];\n const entity = _.find(allProviders, { entityId });\n if (entity) {\n try {\n const providerResponse = await getProviderByLocationAndId(\n entity.location,\n entity._id\n );\n /**\n * Adding entityLocation here to the entityResponse because the import tool\n * needs to know whether the saml2 entity is remote or not (this will be removed\n * from the config before importing see updateSaml2Entity and createSaml2Entity functions).\n * Importing a remote saml2 entity is a slightly different request (see createSaml2Entity).\n */\n providerResponse.entityLocation = entity.location;\n\n if (entity.location === 'remote') {\n // get the xml representation of this entity and add it to the entityResponse;\n const metaDataResponse = await getProviderMetadata(\n providerResponse.entityId\n );\n providerResponse.base64EntityXML = encodeBase64Url(metaDataResponse);\n }\n saml2EntityPromises.push(providerResponse);\n } catch (error) {\n printMessage(error.message, 'error');\n }\n }\n }\n try {\n const saml2EntitiesPromisesResults = await Promise.all(saml2EntityPromises);\n const saml2Entities = [];\n for (const saml2Entity of saml2EntitiesPromisesResults) {\n if (saml2Entity) {\n saml2Entities.push(saml2Entity);\n }\n }\n const samlEntityIds = _.map(\n saml2Entities,\n (saml2EntityConfig) => `${saml2EntityConfig.entityId}|saml2`\n );\n const circlesOfTrust = _.filter(allCirclesOfTrust, (circleOfTrust) => {\n let hasEntityId = false;\n for (const trustedProvider of circleOfTrust.trustedProviders) {\n if (!hasEntityId && samlEntityIds.includes(trustedProvider)) {\n hasEntityId = true;\n }\n }\n return hasEntityId;\n });\n const saml2NodeDependencies = {\n saml2Entities,\n circlesOfTrust,\n };\n return saml2NodeDependencies;\n } catch (error) {\n printMessage(error.message, 'error');\n const saml2NodeDependencies = {\n saml2Entities: [],\n circlesOfTrust: [],\n };\n return saml2NodeDependencies;\n }\n}\n\n// export async function getTreeNodes(treeObject) {\n// const nodeList = Object.entries(treeObject.nodes);\n// const results = await Promise.allSettled(\n// nodeList.map(\n// async ([nodeId, nodeInfo]) => await getNode(nodeId, nodeInfo['nodeType'])\n// )\n// );\n// const nodes = results.filter((r) => r.status === 'fulfilled');\n// nodes.map((f) => {\n// return f.status;\n// });\n// const failedList = results.filter((r) => r.status === 'rejected');\n// return nodes;\n// }\n\n/**\n * Export options\n */\ninterface ExportOptions {\n /**\n * Where applicable, use string arrays to store multi-line text (e.g. scripts).\n */\n useStringArrays: boolean;\n /**\n * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes).\n */\n deps: boolean;\n /**\n * Verbose output during command execution. May or may not produce additional output.\n */\n verbose: boolean;\n}\n\n/**\n * Create export data for a tree/journey with all its nodes and dependencies. The export data can be written to a file as is.\n * @param {string} treeId tree id/name\n * @param {ExportOptions} options export options\n * @returns {Promise<SingleJourneyExportTemplate>} a promise that resolves to an object containing the tree and all its nodes and dependencies\n */\nexport async function exportJourney(\n treeId: string,\n options: ExportOptions = {\n useStringArrays: true,\n deps: true,\n verbose: false,\n }\n): Promise<SingleJourneyExportTemplate> {\n const treeObject = await getTree(treeId);\n const exportData = createSingleJourneyExportTemplate();\n const { useStringArrays, deps, verbose } = options;\n\n if (verbose) printMessage(`\\n- ${treeObject._id}\\n`, 'info', false);\n\n // Process tree\n if (verbose) printMessage(' - Flow');\n exportData.tree = treeObject;\n if (verbose && treeObject.identityResource)\n printMessage(\n ` - identityResource: ${treeObject.identityResource}`,\n 'info'\n );\n if (verbose) printMessage(` - Done`, 'info');\n\n const nodePromises = [];\n const scriptPromises = [];\n const emailTemplatePromises = [];\n const innerNodePromises = [];\n const saml2ConfigPromises = [];\n let socialProviderPromise = null;\n const themePromise =\n deps &&\n storage.session.getDeploymentType() !== global.CLASSIC_DEPLOYMENT_TYPE_KEY\n ? getThemes().catch((error) => {\n printMessage(error, 'error');\n })\n : null;\n\n let allSaml2Providers = null;\n let allCirclesOfTrust = null;\n let filteredSocialProviders = null;\n const themes = [];\n\n // get all the nodes\n for (const [nodeId, nodeInfo] of Object.entries(treeObject.nodes)) {\n nodePromises.push(getNode(nodeId, nodeInfo['nodeType']));\n }\n if (verbose && nodePromises.length > 0) printMessage(' - Nodes:');\n const nodeObjects = await Promise.all(nodePromises);\n\n // iterate over every node in tree\n for (const nodeObject of nodeObjects) {\n const nodeId = nodeObject._id;\n const nodeType = nodeObject._type._id;\n if (verbose) printMessage(` - ${nodeId} (${nodeType})`, 'info', true);\n exportData.nodes[nodeObject._id] = nodeObject;\n\n // handle script node types\n if (\n deps &&\n scriptedNodes.includes(nodeType) &&\n nodeObject.script !== emptyScriptPlaceholder\n ) {\n scriptPromises.push(getScript(nodeObject.script));\n }\n\n // frodo supports email templates in platform deployments\n if (\n (deps &&\n storage.session.getDeploymentType() ===\n global.CLOUD_DEPLOYMENT_TYPE_KEY) ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY\n ) {\n if (emailTemplateNodes.includes(nodeType)) {\n try {\n const emailTemplate = await getEmailTemplate(\n nodeObject.emailTemplateName\n );\n emailTemplatePromises.push(emailTemplate);\n } catch (error) {\n let message = `${error}`;\n if (error.isAxiosError && error.response.status) {\n message = error.response.statusText;\n }\n printMessage(\n `\\n${message}: Email Template \"${nodeObject.emailTemplateName}\"`,\n 'error'\n );\n }\n }\n }\n\n // handle SAML2 node dependencies\n if (deps && nodeType === 'product-Saml2Node') {\n if (!allSaml2Providers) {\n // eslint-disable-next-line no-await-in-loop\n allSaml2Providers = (await getProviders()).result;\n }\n if (!allCirclesOfTrust) {\n // eslint-disable-next-line no-await-in-loop\n allCirclesOfTrust = (await getCirclesOfTrust()).result;\n }\n saml2ConfigPromises.push(\n getSaml2NodeDependencies(\n nodeObject,\n allSaml2Providers,\n allCirclesOfTrust\n )\n );\n }\n\n // If this is a SocialProviderHandlerNode get each enabled social identity provider.\n if (\n deps &&\n !socialProviderPromise &&\n nodeType === 'SocialProviderHandlerNode'\n ) {\n socialProviderPromise = getSocialIdentityProviders();\n }\n\n // If this is a SelectIdPNode and filteredProviters is not already set to empty array set filteredSocialProviers.\n if (deps && !filteredSocialProviders && nodeType === 'SelectIdPNode') {\n filteredSocialProviders = filteredSocialProviders || [];\n for (const filteredProvider of nodeObject.filteredProviders) {\n if (!filteredSocialProviders.includes(filteredProvider)) {\n filteredSocialProviders.push(filteredProvider);\n }\n }\n }\n\n // get inner nodes (nodes inside container nodes)\n if (containerNodes.includes(nodeType)) {\n for (const innerNode of nodeObject.nodes) {\n innerNodePromises.push(getNode(innerNode._id, innerNode.nodeType));\n }\n // frodo supports themes in platform deployments\n if (\n (deps &&\n storage.session.getDeploymentType() ===\n global.CLOUD_DEPLOYMENT_TYPE_KEY) ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY\n ) {\n let themeId = false;\n\n if (nodeObject.stage) {\n // see if themeId is part of the stage object\n try {\n themeId = JSON.parse(nodeObject.stage).themeId;\n } catch (e) {\n themeId = false;\n }\n // if the page node's themeId is set the \"old way\" set themeId accordingly\n if (!themeId && nodeObject.stage.indexOf('themeId=') === 0) {\n // eslint-disable-next-line prefer-destructuring\n themeId = nodeObject.stage.split('=')[1];\n }\n }\n\n if (themeId) {\n if (!themes.includes(themeId)) themes.push(themeId);\n }\n }\n }\n }\n\n // Process inner nodes\n if (verbose && innerNodePromises.length > 0) printMessage(' - Inner nodes:');\n const innerNodeDataResults = await Promise.all(innerNodePromises);\n for (const innerNodeObject of innerNodeDataResults) {\n const innerNodeId = innerNodeObject._id;\n const innerNodeType = innerNodeObject._type._id;\n if (verbose)\n printMessage(` - ${innerNodeId} (${innerNodeType})`, 'info', true);\n exportData.innerNodes[innerNodeId] = innerNodeObject;\n\n // handle script node types\n if (deps && scriptedNodes.includes(innerNodeType)) {\n scriptPromises.push(getScript(innerNodeObject.script));\n }\n\n // frodo supports email templates in platform deployments\n if (\n (deps &&\n storage.session.getDeploymentType() ===\n global.CLOUD_DEPLOYMENT_TYPE_KEY) ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY\n ) {\n if (emailTemplateNodes.includes(innerNodeType)) {\n try {\n const emailTemplate = await getEmailTemplate(\n innerNodeObject.emailTemplateName\n );\n emailTemplatePromises.push(emailTemplate);\n } catch (error) {\n let message = `${error}`;\n if (error.isAxiosError && error.response.status) {\n message = error.response.statusText;\n }\n printMessage(\n `\\n${message}: Email Template \"${innerNodeObject.emailTemplateName}\"`,\n 'error'\n );\n }\n }\n }\n\n // handle SAML2 node dependencies\n if (deps && innerNodeType === 'product-Saml2Node') {\n printMessage('SAML2 inner node', 'error');\n if (!allSaml2Providers) {\n // eslint-disable-next-line no-await-in-loop\n allSaml2Providers = (await getProviders()).result;\n }\n if (!allCirclesOfTrust) {\n // eslint-disable-next-line no-await-in-loop\n allCirclesOfTrust = (await getCirclesOfTrust()).result;\n }\n saml2ConfigPromises.push(\n getSaml2NodeDependencies(\n innerNodeObject,\n allSaml2Providers,\n allCirclesOfTrust\n )\n );\n }\n\n // If this is a SocialProviderHandlerNode get each enabled social identity provider.\n if (\n deps &&\n !socialProviderPromise &&\n innerNodeType === 'SocialProviderHandlerNode'\n ) {\n socialProviderPromise = getSocialIdentityProviders();\n }\n\n // If this is a SelectIdPNode and filteredProviters is not already set to empty array set filteredSocialProviers.\n if (\n deps &&\n !filteredSocialProviders &&\n innerNodeType === 'SelectIdPNode' &&\n innerNodeObject.filteredProviders\n ) {\n filteredSocialProviders = filteredSocialProviders || [];\n for (const filteredProvider of innerNodeObject.filteredProviders) {\n if (!filteredSocialProviders.includes(filteredProvider)) {\n filteredSocialProviders.push(filteredProvider);\n }\n }\n }\n }\n\n // Process email templates\n if (verbose && emailTemplatePromises.length > 0)\n printMessage(' - Email templates:');\n const settledEmailTemplatePromises = await Promise.allSettled(\n emailTemplatePromises\n );\n for (const settledPromise of settledEmailTemplatePromises) {\n if (settledPromise.status === 'fulfilled' && settledPromise.value) {\n if (verbose)\n printMessage(\n ` - ${settledPromise.value._id.split('/')[1]}${\n settledPromise.value.displayName\n ? ` (${settledPromise.value.displayName})`\n : ''\n }`,\n 'info',\n true\n );\n exportData.emailTemplates[settledPromise.value._id.split('/')[1]] =\n settledPromise.value;\n }\n }\n\n // Process SAML2 providers and circles of trust\n const saml2NodeDependencies = await Promise.all(saml2ConfigPromises);\n for (const saml2NodeDependency of saml2NodeDependencies) {\n if (saml2NodeDependency) {\n if (verbose) printMessage(' - SAML2 entity providers:');\n for (const saml2Entity of saml2NodeDependency.saml2Entities) {\n if (verbose)\n printMessage(\n ` - ${saml2Entity.entityLocation} ${saml2Entity.entityId}`,\n 'info'\n );\n exportData.saml2Entities[saml2Entity._id] = saml2Entity;\n }\n if (verbose) printMessage(' - SAML2 circles of trust:');\n for (const circleOfTrust of saml2NodeDependency.circlesOfTrust) {\n if (verbose) printMessage(` - ${circleOfTrust._id}`, 'info');\n exportData.circlesOfTrust[circleOfTrust._id] = circleOfTrust;\n }\n }\n }\n\n // Process socialIdentityProviders\n const socialProviders = await Promise.resolve(socialProviderPromise);\n if (socialProviders) {\n if (verbose) printMessage(' - OAuth2/OIDC (social) identity providers:');\n for (const socialProvider of socialProviders.result) {\n // If the list of socialIdentityProviders needs to be filtered based on the\n // filteredProviders property of a SelectIdPNode do it here.\n if (\n socialProvider &&\n (!filteredSocialProviders ||\n filteredSocialProviders.length === 0 ||\n filteredSocialProviders.includes(socialProvider._id))\n ) {\n if (verbose) printMessage(` - ${socialProvider._id}`, 'info');\n scriptPromises.push(getScript(socialProvider.transform));\n exportData.socialIdentityProviders[socialProvider._id] = socialProvider;\n }\n }\n }\n\n // Process scripts\n if (verbose && scriptPromises.length > 0) printMessage(' - Scripts:');\n const scriptObjects = await Promise.all(scriptPromises);\n for (const scriptObject of scriptObjects) {\n if (scriptObject) {\n if (verbose)\n printMessage(\n ` - ${scriptObject._id} (${scriptObject.name})`,\n 'info',\n true\n );\n scriptObject.script = useStringArrays\n ? convertBase64TextToArray(scriptObject.script)\n : JSON.stringify(decode(scriptObject.script));\n exportData.scripts[scriptObject._id] = scriptObject;\n }\n }\n\n // Process themes\n if (themePromise) {\n if (verbose) printMessage(' - Themes:');\n try {\n const themePromiseResults = await Promise.resolve(themePromise);\n for (const themeObject of themePromiseResults) {\n if (\n themeObject &&\n // has the theme been specified by id or name in a page node?\n (themes.includes(themeObject._id) ||\n themes.includes(themeObject.name) ||\n // has this journey been linked to a theme?\n themeObject.linkedTrees?.includes(treeObject._id))\n ) {\n if (verbose)\n printMessage(\n ` - ${themeObject._id} (${themeObject.name})`,\n 'info'\n );\n exportData.themes.push(themeObject);\n }\n }\n } catch (error) {\n printMessage(error, 'error');\n printMessage('Error handling themes: ' + error.message, 'error');\n }\n }\n\n return exportData;\n}\n\n/**\n * Get all the journeys/trees without all their nodes and dependencies.\n * @returns {Promise<unknown[]>} a promise that resolves to an array of journey objects\n */\nexport async function getJourneys(): Promise<unknown[]> {\n let journeys = [];\n try {\n journeys = (await getTrees()).result;\n } catch (error) {\n printMessage(`${error.message}`, 'error');\n printMessage(error.response.data, 'error');\n }\n journeys.sort((a, b) => a._id.localeCompare(b._id));\n return journeys;\n}\n\n/**\n * Export journey by id/name to file\n * @param {string} journeyId journey id/name\n * @param {string} file optional export file name\n * @param {ExportOptions} options export options\n */\nexport async function exportJourneyToFile(\n journeyId: string,\n file: string,\n options: ExportOptions\n): Promise<void> {\n const { verbose } = options;\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(journeyId, 'journey');\n }\n if (!verbose)\n createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');\n try {\n const fileData: SingleJourneyExportTemplate = await exportJourney(\n journeyId,\n options\n );\n if (verbose)\n createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `Exported ${journeyId['brightCyan']} to ${fileName['brightCyan']}.`,\n 'success'\n );\n } catch (error) {\n if (verbose)\n createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');\n stopProgressIndicator(\n `Error exporting journey ${journeyId}: ${error}`,\n 'fail'\n );\n }\n}\n\n/**\n * Export all journeys to file\n * @param {string} file optional export file name\n * @param {ExportOptions} options export options\n */\nexport async function exportJourneysToFile(\n file: string,\n options: ExportOptions\n): Promise<void> {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(`all${getRealmString()}Journeys`, 'journeys');\n }\n const trees = (await getTrees()).result;\n const fileData: MultiJourneyExportTemplate =\n createMultiJourneyExportTemplate();\n createProgressIndicator(trees.length, 'Exporting journeys...');\n for (const tree of trees) {\n updateProgressIndicator(`${tree._id}`);\n try {\n const exportData = await exportJourney(tree._id, options);\n delete exportData.meta;\n fileData.trees[tree._id] = exportData;\n } catch (error) {\n printMessage(`Error exporting journey ${tree._id}: ${error}`, 'error');\n }\n }\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(`Exported to ${fileName}`);\n}\n\n/**\n * Export all journeys to separate files\n * @param {ExportOptions} options export options\n */\nexport async function exportJourneysToFiles(\n options: ExportOptions\n): Promise<void> {\n const trees = (await getTrees()).result;\n createProgressIndicator(trees.length, 'Exporting journeys...');\n for (const tree of trees) {\n updateProgressIndicator(`${tree._id}`);\n const fileName = getTypedFilename(`${tree._id}`, 'journey');\n try {\n const exportData: SingleJourneyExportTemplate = await exportJourney(\n tree._id,\n options\n );\n saveJsonToFile(exportData, fileName);\n } catch (error) {\n // do we need to report status here?\n }\n }\n stopProgressIndicator('Done');\n}\n\n/**\n * Import options\n */\ninterface ImportOptions {\n /**\n * Generate new UUIDs for all nodes during import.\n */\n reUuid: boolean;\n /**\n * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes).\n */\n deps: boolean;\n /**\n * Verbose output during command execution. May or may not produce additional output.\n */\n verbose: boolean;\n}\n\n/**\n * Helper to import a tree with all dependencies from an import data object (typically read from a file)\n * @param {SingleJourneyExportTemplate} treeObject tree object containing tree and all its dependencies\n * @param {ImportOptions} options import options\n */\nexport async function importJourney(\n treeObject: SingleJourneyExportTemplate,\n options: ImportOptions\n): Promise<void> {\n const { reUuid, deps, verbose } = options;\n if (verbose) printMessage(`\\n- ${treeObject.tree._id}\\n`, 'info', false);\n let newUuid = '';\n const uuidMap = {};\n const treeId = treeObject.tree._id;\n\n // Process scripts\n if (\n deps &&\n treeObject.scripts &&\n Object.entries(treeObject.scripts).length > 0\n ) {\n if (verbose) printMessage(' - Scripts:');\n for (const [scriptId, scriptObject] of Object.entries(treeObject.scripts)) {\n if (verbose)\n printMessage(\n ` - ${scriptId} (${scriptObject['name']})`,\n 'info',\n false\n );\n // is the script stored as an array of strings or just b64 blob?\n if (Array.isArray(scriptObject['script'])) {\n scriptObject['script'] = convertTextArrayToBase64(\n scriptObject['script']\n );\n } else if (!isBase64Encoded(scriptObject['script'])) {\n scriptObject['script'] = encode(JSON.parse(scriptObject['script']));\n }\n if ((await createOrUpdateScript(scriptId, scriptObject)) == null) {\n throw new Error(\n `Error importing script ${scriptObject['name']} (${scriptId}) in journey ${treeId}`\n );\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process email templates\n if (\n deps &&\n treeObject.emailTemplates &&\n Object.entries(treeObject.emailTemplates).length > 0\n ) {\n if (verbose) printMessage(' - Email templates:');\n for (const [templateId, templateData] of Object.entries(\n treeObject.emailTemplates\n )) {\n if (verbose) printMessage(` - ${templateId}`, 'info', false);\n try {\n await putEmailTemplate(templateId, templateData);\n } catch (error) {\n printMessage(error.response.data, 'error');\n throw new Error(`Error importing email templates: ${error.message}`);\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process themes\n if (deps && treeObject.themes && treeObject.themes.length > 0) {\n if (verbose) printMessage(' - Themes:');\n const themes = {};\n for (const theme of treeObject.themes) {\n if (verbose)\n printMessage(` - ${theme['_id']} (${theme['name']})`, 'info');\n themes[theme['_id']] = theme;\n }\n try {\n await putThemes(themes);\n } catch (error) {\n throw new Error(`Error importing themes: ${error.message}`);\n }\n }\n\n // Process social providers\n if (\n deps &&\n treeObject.socialIdentityProviders &&\n Object.entries(treeObject.socialIdentityProviders).length > 0\n ) {\n if (verbose) printMessage(' - OAuth2/OIDC (social) identity providers:');\n for (const [providerId, providerData] of Object.entries(\n treeObject.socialIdentityProviders\n )) {\n if (verbose) printMessage(` - ${providerId}`, 'info');\n try {\n await putProviderByTypeAndId(\n providerData['_type']['_id'],\n providerId,\n providerData\n );\n } catch (importError) {\n if (\n importError.response?.status === 500 &&\n importError.response?.data?.message ===\n 'Unable to update SMS config: Data validation failed for the attribute, Redirect after form post URL'\n ) {\n providerData['redirectAfterFormPostURI'] = '';\n try {\n await putProviderByTypeAndId(\n providerData['_type']['_id'],\n providerId,\n providerData\n );\n } catch (importError2) {\n printMessage(importError.response?.data || importError, 'error');\n throw new Error(\n `Error importing provider ${providerId} in journey ${treeId}: ${importError}`\n );\n }\n } else {\n printMessage(importError.response?.data || importError, 'error');\n throw new Error(\n `\\nError importing provider ${providerId} in journey ${treeId}: ${importError}`\n );\n }\n }\n }\n }\n\n // Process saml providers\n if (\n deps &&\n treeObject.saml2Entities &&\n Object.entries(treeObject.saml2Entities).length > 0\n ) {\n if (verbose) printMessage(' - SAML2 entity providers:');\n for (const [, providerData] of Object.entries(treeObject.saml2Entities)) {\n delete providerData['_rev'];\n const entityId = providerData['entityId'];\n const entityLocation = providerData['entityLocation'];\n if (verbose) printMessage(` - ${entityLocation} ${entityId}`, 'info');\n let metaData = null;\n if (entityLocation === 'remote') {\n if (Array.isArray(providerData['base64EntityXML'])) {\n metaData = convertTextArrayToBase64Url(\n providerData['base64EntityXML']\n );\n } else {\n metaData = providerData['base64EntityXML'];\n }\n }\n delete providerData['entityLocation'];\n delete providerData['base64EntityXML'];\n // create the provider if it doesn't already exist, or just update it\n if (\n (await findProviders(`entityId eq '${entityId}'`, 'location'))\n .resultCount === 0\n ) {\n await createProvider(entityLocation, providerData, metaData).catch(\n (createProviderErr) => {\n printMessage(\n createProviderErr.response?.data || createProviderErr,\n 'error'\n );\n throw new Error(`Error creating provider ${entityId}`);\n }\n );\n } else {\n await updateProvider(entityLocation, providerData).catch(\n (updateProviderErr) => {\n printMessage(\n updateProviderErr.response?.data || updateProviderErr,\n 'error'\n );\n throw new Error(`Error updating provider ${entityId}`);\n }\n );\n }\n }\n }\n\n // Process circles of trust\n if (\n deps &&\n treeObject.circlesOfTrust &&\n Object.entries(treeObject.circlesOfTrust).length > 0\n ) {\n if (verbose) printMessage(' - SAML2 circles of trust:');\n for (const [cotId, cotData] of Object.entries(treeObject.circlesOfTrust)) {\n delete cotData['_rev'];\n if (verbose) printMessage(` - ${cotId}`, 'info');\n try {\n await createCircleOfTrust(cotData);\n } catch (createCotErr) {\n if (\n createCotErr.response?.status === 409 ||\n createCotErr.response?.status === 500\n ) {\n try {\n await updateCircleOfTrust(cotId, cotData);\n } catch (updateCotErr) {\n printMessage(createCotErr.response?.data || createCotErr, 'error');\n printMessage(updateCotErr.response?.data || updateCotErr, 'error');\n throw new Error(`Error creating/updating circle of trust ${cotId}`);\n }\n } else {\n printMessage(createCotErr.response?.data || createCotErr, 'error');\n throw new Error(`Error creating circle of trust ${cotId}`);\n }\n }\n }\n }\n\n // Process inner nodes\n let innerNodes = {};\n if (\n treeObject.innerNodes &&\n Object.entries(treeObject.innerNodes).length > 0\n ) {\n innerNodes = treeObject.innerNodes;\n }\n // old export file format\n else if (\n treeObject.innernodes &&\n Object.entries(treeObject.innernodes).length > 0\n ) {\n innerNodes = treeObject.innernodes;\n }\n if (Object.entries(innerNodes).length > 0) {\n if (verbose) printMessage(' - Inner nodes:', 'text', true);\n for (const [innerNodeId, innerNodeData] of Object.entries(innerNodes)) {\n delete innerNodeData['_rev'];\n const nodeType = innerNodeData['_type']['_id'];\n if (!reUuid) {\n newUuid = innerNodeId;\n } else {\n newUuid = uuidv4();\n uuidMap[innerNodeId] = newUuid;\n }\n innerNodeData['_id'] = newUuid;\n\n if (verbose)\n printMessage(\n ` - ${newUuid}${reUuid ? '*' : ''} (${nodeType})`,\n 'info',\n false\n );\n\n // If the node has an identityResource config setting\n // and the identityResource ends in 'user'\n // and the node's identityResource is the same as the tree's identityResource\n // change it to the current realm managed user identityResource otherwise leave it alone.\n if (\n innerNodeData['identityResource'] &&\n innerNodeData['identityResource'].endsWith('user') &&\n innerNodeData['identityResource'] === treeObject.tree.identityResource\n ) {\n innerNodeData['identityResource'] = `managed/${getRealmManagedUser()}`;\n if (verbose)\n printMessage(\n `\\n - identityResource: ${innerNodeData['identityResource']}`,\n 'info',\n false\n );\n }\n try {\n await putNode(newUuid, nodeType, innerNodeData);\n } catch (nodeImportError) {\n if (\n nodeImportError.response.status === 400 &&\n nodeImportError.response.data.message ===\n 'Data validation failed for the attribute, Script'\n ) {\n throw new Error(\n `Missing script ${\n innerNodeData['script']\n } referenced by inner node ${innerNodeId}${\n innerNodeId === newUuid ? '' : ` [${newUuid}]`\n } (${innerNodeData['_type']['_id']}) in journey ${treeId}.`\n );\n } else {\n printMessage(nodeImportError.response.data, 'error');\n throw new Error(\n `Error importing inner node ${innerNodeId}${\n innerNodeId === newUuid ? '' : ` [${newUuid}]`\n } in journey ${treeId}`\n );\n }\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process nodes\n if (treeObject.nodes && Object.entries(treeObject.nodes).length > 0) {\n if (verbose) printMessage(' - Nodes:');\n // eslint-disable-next-line prefer-const\n for (let [nodeId, nodeData] of Object.entries(treeObject.nodes)) {\n delete nodeData['_rev'];\n const nodeType = nodeData['_type']['_id'];\n if (!reUuid) {\n newUuid = nodeId;\n } else {\n newUuid = uuidv4();\n uuidMap[nodeId] = newUuid;\n }\n nodeData['_id'] = newUuid;\n\n if (nodeType === 'PageNode' && reUuid) {\n for (const [, inPageNodeData] of Object.entries(nodeData['nodes'])) {\n const currentId = inPageNodeData['_id'];\n nodeData = JSON.parse(\n replaceAll(JSON.stringify(nodeData), currentId, uuidMap[currentId])\n );\n }\n }\n\n if (verbose)\n printMessage(\n ` - ${newUuid}${reUuid ? '*' : ''} (${nodeType})`,\n 'info',\n false\n );\n\n // If the node has an identityResource config setting\n // and the identityResource ends in 'user'\n // and the node's identityResource is the same as the tree's identityResource\n // change it to the current realm managed user identityResource otherwise leave it alone.\n if (\n nodeData['identityResource'] &&\n nodeData['identityResource'].endsWith('user') &&\n nodeData['identityResource'] === treeObject.tree.identityResource\n ) {\n nodeData['identityResource'] = `managed/${getRealmManagedUser()}`;\n if (verbose)\n printMessage(\n `\\n - identityResource: ${nodeData['identityResource']}`,\n 'info',\n false\n );\n }\n try {\n await putNode(newUuid, nodeType, nodeData);\n } catch (nodeImportError) {\n if (\n nodeImportError.response.status === 400 &&\n nodeImportError.response.data.message ===\n 'Data validation failed for the attribute, Script'\n ) {\n throw new Error(\n `Missing script ${nodeData['script']} referenced by node ${nodeId}${\n nodeId === newUuid ? '' : ` [${newUuid}]`\n } (${nodeData['_type']['_id']}) in journey ${treeId}.`\n );\n } else {\n printMessage(nodeImportError.response.data, 'error');\n throw new Error(\n `Error importing node ${nodeId}${\n nodeId === newUuid ? '' : ` [${newUuid}]`\n } in journey ${treeId}`\n );\n }\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process tree\n if (verbose) printMessage(' - Flow');\n\n if (reUuid) {\n let journeyText = JSON.stringify(treeObject.tree, null, 2);\n for (const [oldId, newId] of Object.entries(uuidMap)) {\n journeyText = replaceAll(journeyText, oldId, newId);\n }\n treeObject.tree = JSON.parse(journeyText);\n }\n\n // If the tree has an identityResource config setting\n // and the identityResource ends in 'user'\n // Set the identityResource for the tree to the selected resource.\n if (\n treeObject.tree.identityResource &&\n (treeObject.tree['identityResource'] as string).endsWith('user')\n ) {\n treeObject.tree.identityResource = `managed/${getRealmManagedUser()}`;\n if (verbose)\n printMessage(\n ` - identityResource: ${treeObject.tree.identityResource}`,\n 'info',\n false\n );\n }\n\n delete treeObject.tree._rev;\n try {\n await putTree(treeObject.tree._id as string, treeObject.tree);\n if (verbose) printMessage(`\\n - Done`, 'info', true);\n } catch (importError) {\n if (\n importError.response?.status === 400 &&\n importError.response?.data?.message === 'Invalid attribute specified.'\n ) {\n const { validAttributes } = importError.response.data.detail;\n validAttributes.push('_id');\n for (const attribute of Object.keys(treeObject.tree)) {\n if (!validAttributes.includes(attribute)) {\n if (verbose)\n printMessage(\n `\\n - Removing invalid attribute: ${attribute}`,\n 'info',\n false\n );\n delete treeObject.tree[attribute];\n }\n }\n try {\n await putTree(treeObject.tree._id as string, treeObject.tree);\n if (verbose) printMessage(`\\n - Done`, 'info', true);\n } catch (importError2) {\n printMessage(importError2.response.data, 'error');\n throw new Error(`Error importing journey flow ${treeId}`);\n }\n } else {\n printMessage(importError.response?.data || importError, 'error');\n console.dir(importError.response?.data || importError);\n throw new Error(`\\nError importing journey flow ${treeId}`);\n }\n }\n}\n\n/**\n * Resolve journey dependencies\n * @param {Map} installedJorneys Map of installed journeys\n * @param {Map} journeyMap Map of journeys to resolve dependencies for\n * @param {[String]} unresolvedJourneys Map to hold the names of unresolved journeys and their dependencies\n * @param {[String]} resolvedJourneys Array to hold the names of resolved journeys\n * @param {int} index Depth of recursion\n */\nasync function resolveDependencies(\n installedJorneys,\n journeyMap,\n unresolvedJourneys,\n resolvedJourneys,\n index = -1\n) {\n let before = -1;\n let after = index;\n if (index !== -1) {\n before = index;\n }\n\n for (const tree in journeyMap) {\n if ({}.hasOwnProperty.call(journeyMap, tree)) {\n const dependencies = [];\n for (const node in journeyMap[tree].nodes) {\n if (\n journeyMap[tree].nodes[node]._type._id === 'InnerTreeEvaluatorNode'\n ) {\n dependencies.push(journeyMap[tree].nodes[node].tree);\n }\n }\n let allResolved = true;\n for (const dependency of dependencies) {\n if (\n !resolvedJourneys.includes(dependency) &&\n !installedJorneys.includes(dependency)\n ) {\n allResolved = false;\n }\n }\n if (allResolved) {\n if (resolvedJourneys.indexOf(tree) === -1) resolvedJourneys.push(tree);\n // remove from unresolvedJourneys array\n // for (let i = 0; i < unresolvedJourneys.length; i += 1) {\n // if (unresolvedJourneys[i] === tree) {\n // unresolvedJourneys.splice(i, 1);\n // i -= 1;\n // }\n // }\n delete unresolvedJourneys[tree];\n // } else if (!unresolvedJourneys.includes(tree)) {\n } else {\n // unresolvedJourneys.push(tree);\n unresolvedJourneys[tree] = dependencies;\n }\n }\n }\n after = Object.keys(unresolvedJourneys).length;\n if (index !== -1 && after === before) {\n // This is the end, no progress was made since the last recursion\n // printMessage(\n // `Journeys with unresolved dependencies: ${unresolvedJourneys}`,\n // 'error'\n // );\n } else if (after > 0) {\n resolveDependencies(\n installedJorneys,\n journeyMap,\n unresolvedJourneys,\n resolvedJourneys,\n after\n );\n }\n}\n\n/**\n * Import a journey from file\n * @param {string} journeyId journey id/name\n * @param {string} file import file name\n * @param {ImportOptions} options import options\n */\nexport async function importJourneyFromFile(\n journeyId: string,\n file: string,\n options: ImportOptions\n) {\n const { verbose } = options;\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n let journeyData = JSON.parse(data);\n // check if this is a file with multiple trees and get journey by id\n if (journeyData.trees && journeyData.trees[journeyId]) {\n journeyData = journeyData.trees[journeyId];\n } else if (journeyData.trees) {\n journeyData = null;\n }\n\n // if a journeyId was specified, only import the matching journey\n if (journeyData && journeyId === journeyData.tree._id) {\n // attempt dependency resolution for single tree import\n const installedJourneys = (await getTrees()).result.map((x) => x._id);\n const unresolvedJourneys = {};\n const resolvedJourneys = [];\n createProgressIndicator(\n undefined,\n 'Resolving dependencies',\n 'indeterminate'\n );\n await resolveDependencies(\n installedJourneys,\n { [journeyId]: journeyData },\n unresolvedJourneys,\n resolvedJourneys\n );\n if (Object.keys(unresolvedJourneys).length === 0) {\n stopProgressIndicator(`Resolved all dependencies.`, 'success');\n\n if (!verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n importJourney(journeyData, options)\n .then(() => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`Imported ${journeyId}.`, 'success');\n })\n .catch((importError) => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`${importError}`, 'fail');\n });\n } else {\n stopProgressIndicator(`Unresolved dependencies:`, 'fail');\n for (const journey of Object.keys(unresolvedJourneys)) {\n printMessage(\n ` ${journey} requires ${unresolvedJourneys[journey]}`,\n 'error'\n );\n }\n }\n // end dependency resolution for single tree import\n } else {\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`${journeyId} not found!`, 'fail');\n }\n });\n}\n\n/**\n * Import first journey from file\n * @param {string} file import file name\n * @param {ImportOptions} options import options\n */\nexport async function importFirstJourneyFromFile(\n file: string,\n options: ImportOptions\n) {\n const { verbose } = options;\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n let journeyData = _.cloneDeep(JSON.parse(data));\n let journeyId = null;\n // single tree\n if (journeyData.tree) {\n journeyId = _.cloneDeep(journeyData.tree._id);\n }\n // multiple trees, so get the first tree\n else if (journeyData.trees) {\n for (const treeId in journeyData.trees) {\n if (Object.hasOwnProperty.call(journeyData.trees, treeId)) {\n journeyId = treeId;\n journeyData = journeyData.trees[treeId];\n break;\n }\n }\n }\n\n // if a journeyId was specified, only import the matching journey\n if (journeyData && journeyId) {\n // attempt dependency resolution for single tree import\n const installedJourneys = (await getTrees()).result.map((x) => x._id);\n const unresolvedJourneys = {};\n const resolvedJourneys = [];\n createProgressIndicator(\n undefined,\n 'Resolving dependencies',\n 'indeterminate'\n );\n await resolveDependencies(\n installedJourneys,\n { [journeyId]: journeyData },\n unresolvedJourneys,\n resolvedJourneys\n );\n if (Object.keys(unresolvedJourneys).length === 0) {\n stopProgressIndicator(`Resolved all dependencies.`, 'success');\n\n if (!verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n importJourney(journeyData, options)\n .then(() => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`Imported ${journeyId}.`, 'success');\n })\n .catch((importError) => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`${importError}`, 'fail');\n });\n } else {\n stopProgressIndicator(`Unresolved dependencies:`, 'fail');\n for (const journey of Object.keys(unresolvedJourneys)) {\n printMessage(\n ` ${journey} requires ${unresolvedJourneys[journey]}`,\n 'error'\n );\n }\n }\n } else {\n createProgressIndicator(undefined, `Importing...`, 'indeterminate');\n stopProgressIndicator(`No journeys found!`, 'fail');\n }\n // end dependency resolution for single tree import\n });\n}\n\n/**\n * Helper to import multiple trees from a tree map\n * @param {Object} treesMap map of trees object\n * @param {ImportOptions} options import options\n */\nasync function importAllJourneys(\n treesMap: MultiJourneyExportTemplate,\n options: ImportOptions\n) {\n const installedJourneys = (await getTrees()).result.map((x) => x._id);\n const unresolvedJourneys = {};\n const resolvedJourneys = [];\n createProgressIndicator(undefined, 'Resolving dependencies', 'indeterminate');\n await resolveDependencies(\n installedJourneys,\n treesMap,\n unresolvedJourneys,\n resolvedJourneys\n );\n if (Object.keys(unresolvedJourneys).length === 0) {\n stopProgressIndicator(`Resolved all dependencies.`, 'success');\n } else {\n stopProgressIndicator(\n `${\n Object.keys(unresolvedJourneys).length\n } journeys with unresolved dependencies:`,\n 'fail'\n );\n for (const journey of Object.keys(unresolvedJourneys)) {\n printMessage(\n ` - ${journey} requires ${unresolvedJourneys[journey]}`,\n 'info'\n );\n }\n }\n createProgressIndicator(resolvedJourneys.length, 'Importing');\n for (const tree of resolvedJourneys) {\n try {\n // eslint-disable-next-line no-await-in-loop\n await importJourney(treesMap[tree], options);\n updateProgressIndicator(`${tree}`);\n } catch (error) {\n printMessage(`\\n${error.message}`, 'error');\n }\n }\n stopProgressIndicator('Done');\n}\n\n/**\n * Import all journeys from file\n * @param {string} file import file name\n * @param {ImportOptions} options import options\n */\nexport async function importJourneysFromFile(\n file: string,\n options: ImportOptions\n) {\n fs.readFile(file, 'utf8', (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n importAllJourneys(fileData.trees, options);\n });\n}\n\n/**\n * Import all journeys from separate files\n * @param {ImportOptions} options import options\n */\nexport async function importJourneysFromFiles(options: ImportOptions) {\n const names = fs.readdirSync('.');\n const jsonFiles = names.filter((name) =>\n name.toLowerCase().endsWith('.journey.json')\n );\n const allJourneysData = { trees: {} };\n for (const file of jsonFiles) {\n const journeyData = JSON.parse(fs.readFileSync(file, 'utf8'));\n allJourneysData.trees[journeyData.tree._id] = journeyData;\n }\n importAllJourneys(\n allJourneysData.trees as MultiJourneyExportTemplate,\n options\n );\n}\n\n/**\n * Describe a tree\n * @param {Object} journeyData tree\n * @returns {Object} an object describing the tree\n */\nexport function describeJourney(journeyData) {\n const treeMap = {};\n const nodeTypeMap = {};\n const scriptsMap = {};\n const emailTemplatesMap = {};\n treeMap['treeName'] = journeyData.tree._id;\n for (const [, nodeData] of Object.entries(journeyData.nodes)) {\n if (nodeTypeMap[nodeData['_type']['_id']]) {\n nodeTypeMap[nodeData['_type']['_id']] += 1;\n } else {\n nodeTypeMap[nodeData['_type']['_id']] = 1;\n }\n }\n\n for (const [, nodeData] of Object.entries(journeyData.innerNodes)) {\n if (nodeTypeMap[nodeData['_type']['_id']]) {\n nodeTypeMap[nodeData['_type']['_id']] += 1;\n } else {\n nodeTypeMap[nodeData['_type']['_id']] = 1;\n }\n }\n\n for (const [, scriptData] of Object.entries(journeyData.scripts)) {\n scriptsMap[scriptData['name']] = scriptData['description'];\n }\n\n for (const [id, data] of Object.entries(journeyData.emailTemplates)) {\n emailTemplatesMap[id] = data['displayName'];\n }\n\n treeMap['nodeTypes'] = nodeTypeMap;\n treeMap['scripts'] = scriptsMap;\n treeMap['emailTemplates'] = emailTemplatesMap;\n\n printMessage(`\\nJourney: ${treeMap['treeName']}`, 'data');\n printMessage('========');\n printMessage('\\nNodes:', 'data');\n if (Object.entries(treeMap['nodeTypes']).length) {\n for (const [name, count] of Object.entries(treeMap['nodeTypes'])) {\n printMessage(`- ${name}: ${count}`, 'data');\n }\n }\n if (Object.entries(treeMap['scripts']).length) {\n printMessage('\\nScripts:', 'data');\n for (const [name, desc] of Object.entries(treeMap['scripts'])) {\n printMessage(`- ${name}: ${desc}`, 'data');\n }\n }\n if (Object.entries(treeMap['emailTemplates']).length) {\n printMessage('\\nEmail Templates:', 'data');\n for (const [id] of Object.entries(treeMap['emailTemplates'])) {\n printMessage(`- ${id}`, 'data');\n }\n }\n\n return treeMap;\n}\n\n/**\n * Find all node configuration objects that are no longer referenced by any tree\n * @returns {Promise<unknown[]>} a promise that resolves to an array of orphaned nodes\n */\nexport async function findOrphanedNodes(): Promise<unknown[]> {\n const allNodes = [];\n const orphanedNodes = [];\n let types = [];\n const allJourneys = (await getTrees()).result;\n let errorMessage = '';\n const errorTypes = [];\n\n createProgressIndicator(\n undefined,\n `Counting total nodes...`,\n 'indeterminate'\n );\n try {\n types = (await getNodeTypes()).result;\n } catch (error) {\n printMessage('Error retrieving all available node types:', 'error');\n printMessage(error.response.data, 'error');\n return [];\n }\n for (const type of types) {\n try {\n // eslint-disable-next-line no-await-in-loop, no-loop-func\n const nodes = (await getNodesByType(type._id)).result;\n for (const node of nodes) {\n allNodes.push(node);\n updateProgressIndicator(\n `${allNodes.length} total nodes${errorMessage}`\n );\n }\n } catch (error) {\n errorTypes.push(type._id);\n errorMessage = ` (Skipped type(s): ${errorTypes})`['yellow'];\n updateProgressIndicator(`${allNodes.length} total nodes${errorMessage}`);\n }\n }\n if (errorTypes.length > 0) {\n stopProgressIndicator(\n `${allNodes.length} total nodes${errorMessage}`,\n 'warn'\n );\n } else {\n stopProgressIndicator(`${allNodes.length} total nodes`, 'success');\n }\n\n createProgressIndicator(\n undefined,\n 'Counting active nodes...',\n 'indeterminate'\n );\n const activeNodes = [];\n for (const journey of allJourneys) {\n for (const nodeId in journey.nodes) {\n if ({}.hasOwnProperty.call(journey.nodes, nodeId)) {\n activeNodes.push(nodeId);\n updateProgressIndicator(`${activeNodes.length} active nodes`);\n const node = journey.nodes[nodeId];\n if (containerNodes.includes(node.nodeType)) {\n const containerNode = await getNode(nodeId, node.nodeType);\n for (const innerNode of containerNode.nodes) {\n activeNodes.push(innerNode._id);\n updateProgressIndicator(`${activeNodes.length} active nodes`);\n }\n }\n }\n }\n }\n stopProgressIndicator(`${activeNodes.length} active nodes`, 'success');\n\n createProgressIndicator(\n undefined,\n 'Calculating orphaned nodes...',\n 'indeterminate'\n );\n const diff = allNodes.filter((x) => !activeNodes.includes(x._id));\n for (const orphanedNode of diff) {\n orphanedNodes.push(orphanedNode);\n }\n stopProgressIndicator(`${orphanedNodes.length} orphaned nodes`, 'success');\n return orphanedNodes;\n}\n\n/**\n * Remove orphaned nodes\n * @param {[Object]} orphanedNodes Pass in an array of orphaned node configuration objects to remove\n * @returns {Promise<unknown[]>} a promise that resolves to an array nodes that encountered errors deleting\n */\nexport async function removeOrphanedNodes(\n orphanedNodes: unknown[]\n): Promise<unknown[]> {\n const errorNodes = [];\n createProgressIndicator(orphanedNodes.length, 'Removing orphaned nodes...');\n for (const node of orphanedNodes) {\n updateProgressIndicator(`Removing ${node['_id']}...`);\n try {\n // eslint-disable-next-line no-await-in-loop\n await deleteNode(node['_id'], node['_type']['_id']);\n } catch (deleteError) {\n errorNodes.push(node);\n printMessage(` ${deleteError}`, 'error');\n }\n }\n stopProgressIndicator(`Removed ${orphanedNodes.length} orphaned nodes.`);\n return errorNodes;\n}\n\nconst OOTB_NODE_TYPES_7 = [\n 'AcceptTermsAndConditionsNode',\n 'AccountActiveDecisionNode',\n 'AccountLockoutNode',\n 'AgentDataStoreDecisionNode',\n 'AnonymousSessionUpgradeNode',\n 'AnonymousUserNode',\n 'AttributeCollectorNode',\n 'AttributePresentDecisionNode',\n 'AttributeValueDecisionNode',\n 'AuthLevelDecisionNode',\n 'ChoiceCollectorNode',\n 'ConsentNode',\n 'CookiePresenceDecisionNode',\n 'CreateObjectNode',\n 'CreatePasswordNode',\n 'DataStoreDecisionNode',\n 'DeviceGeoFencingNode',\n 'DeviceLocationMatchNode',\n 'DeviceMatchNode',\n 'DeviceProfileCollectorNode',\n 'DeviceSaveNode',\n 'DeviceTamperingVerificationNode',\n 'DisplayUserNameNode',\n 'EmailSuspendNode',\n 'EmailTemplateNode',\n 'IdentifyExistingUserNode',\n 'IncrementLoginCountNode',\n 'InnerTreeEvaluatorNode',\n 'IotAuthenticationNode',\n 'IotRegistrationNode',\n 'KbaCreateNode',\n 'KbaDecisionNode',\n 'KbaVerifyNode',\n 'LdapDecisionNode',\n 'LoginCountDecisionNode',\n 'MessageNode',\n 'MetadataNode',\n 'MeterNode',\n 'ModifyAuthLevelNode',\n 'OneTimePasswordCollectorDecisionNode',\n 'OneTimePasswordGeneratorNode',\n 'OneTimePasswordSmsSenderNode',\n 'OneTimePasswordSmtpSenderNode',\n 'PageNode',\n 'PasswordCollectorNode',\n 'PatchObjectNode',\n 'PersistentCookieDecisionNode',\n 'PollingWaitNode',\n 'ProfileCompletenessDecisionNode',\n 'ProvisionDynamicAccountNode',\n 'ProvisionIdmAccountNode',\n 'PushAuthenticationSenderNode',\n 'PushResultVerifierNode',\n 'QueryFilterDecisionNode',\n 'RecoveryCodeCollectorDecisionNode',\n 'RecoveryCodeDisplayNode',\n 'RegisterLogoutWebhookNode',\n 'RemoveSessionPropertiesNode',\n 'RequiredAttributesDecisionNode',\n 'RetryLimitDecisionNode',\n 'ScriptedDecisionNode',\n 'SelectIdPNode',\n 'SessionDataNode',\n 'SetFailureUrlNode',\n 'SetPersistentCookieNode',\n 'SetSessionPropertiesNode',\n 'SetSuccessUrlNode',\n 'SocialFacebookNode',\n 'SocialGoogleNode',\n 'SocialNode',\n 'SocialOAuthIgnoreProfileNode',\n 'SocialOpenIdConnectNode',\n 'SocialProviderHandlerNode',\n 'TermsAndConditionsDecisionNode',\n 'TimeSinceDecisionNode',\n 'TimerStartNode',\n 'TimerStopNode',\n 'UsernameCollectorNode',\n 'ValidatedPasswordNode',\n 'ValidatedUsernameNode',\n 'WebAuthnAuthenticationNode',\n 'WebAuthnDeviceStorageNode',\n 'WebAuthnRegistrationNode',\n 'ZeroPageLoginNode',\n 'product-CertificateCollectorNode',\n 'product-CertificateUserExtractorNode',\n 'product-CertificateValidationNode',\n 'product-KerberosNode',\n 'product-ReCaptchaNode',\n 'product-Saml2Node',\n 'product-WriteFederationInformationNode',\n];\n\nconst OOTB_NODE_TYPES_7_1 = [\n 'PushRegistrationNode',\n 'GetAuthenticatorAppNode',\n 'MultiFactorRegistrationOptionsNode',\n 'OptOutMultiFactorAuthenticationNode',\n].concat(OOTB_NODE_TYPES_7);\n\nconst OOTB_NODE_TYPES_7_2 = [\n 'OathRegistrationNode',\n 'OathTokenVerifierNode',\n 'PassthroughAuthenticationNode',\n 'ConfigProviderNode',\n 'DebugNode',\n].concat(OOTB_NODE_TYPES_7_1);\n\nconst OOTB_NODE_TYPES_6_5 = [\n 'AbstractSocialAuthLoginNode',\n 'AccountLockoutNode',\n 'AgentDataStoreDecisionNode',\n 'AnonymousUserNode',\n 'AuthLevelDecisionNode',\n 'ChoiceCollectorNode',\n 'CookiePresenceDecisionNode',\n 'CreatePasswordNode',\n 'DataStoreDecisionNode',\n 'InnerTreeEvaluatorNode',\n 'LdapDecisionNode',\n 'MessageNode',\n 'MetadataNode',\n 'MeterNode',\n 'ModifyAuthLevelNode',\n 'OneTimePasswordCollectorDecisionNode',\n 'OneTimePasswordGeneratorNode',\n 'OneTimePasswordSmsSenderNode',\n 'OneTimePasswordSmtpSenderNode',\n 'PageNode',\n 'PasswordCollectorNode',\n 'PersistentCookieDecisionNode',\n 'PollingWaitNode',\n 'ProvisionDynamicAccountNode',\n 'ProvisionIdmAccountNode',\n 'PushAuthenticationSenderNode',\n 'PushResultVerifierNode',\n 'RecoveryCodeCollectorDecisionNode',\n 'RecoveryCodeDisplayNode',\n 'RegisterLogoutWebhookNode',\n 'RemoveSessionPropertiesNode',\n 'RetryLimitDecisionNode',\n 'ScriptedDecisionNode',\n 'SessionDataNode',\n 'SetFailureUrlNode',\n 'SetPersistentCookieNode',\n 'SetSessionPropertiesNode',\n 'SetSuccessUrlNode',\n 'SocialFacebookNode',\n 'SocialGoogleNode',\n 'SocialNode',\n 'SocialOAuthIgnoreProfileNode',\n 'SocialOpenIdConnectNode',\n 'TimerStartNode',\n 'TimerStopNode',\n 'UsernameCollectorNode',\n 'WebAuthnAuthenticationNode',\n 'WebAuthnRegistrationNode',\n 'ZeroPageLoginNode',\n];\n\nconst OOTB_NODE_TYPES_6 = [\n 'AbstractSocialAuthLoginNode',\n 'AccountLockoutNode',\n 'AgentDataStoreDecisionNode',\n 'AnonymousUserNode',\n 'AuthLevelDecisionNode',\n 'ChoiceCollectorNode',\n 'CookiePresenceDecisionNode',\n 'CreatePasswordNode',\n 'DataStoreDecisionNode',\n 'InnerTreeEvaluatorNode',\n 'LdapDecisionNode',\n 'MessageNode',\n 'MetadataNode',\n 'MeterNode',\n 'ModifyAuthLevelNode',\n 'OneTimePasswordCollectorDecisionNode',\n 'OneTimePasswordGeneratorNode',\n 'OneTimePasswordSmsSenderNode',\n 'OneTimePasswordSmtpSenderNode',\n 'PageNode',\n 'PasswordCollectorNode',\n 'PersistentCookieDecisionNode',\n 'PollingWaitNode',\n 'ProvisionDynamicAccountNode',\n 'ProvisionIdmAccountNode',\n 'PushAuthenticationSenderNode',\n 'PushResultVerifierNode',\n 'RecoveryCodeCollectorDecisionNode',\n 'RecoveryCodeDisplayNode',\n 'RegisterLogoutWebhookNode',\n 'RemoveSessionPropertiesNode',\n 'RetryLimitDecisionNode',\n 'ScriptedDecisionNode',\n 'SessionDataNode',\n 'SetFailureUrlNode',\n 'SetPersistentCookieNode',\n 'SetSessionPropertiesNode',\n 'SetSuccessUrlNode',\n 'SocialFacebookNode',\n 'SocialGoogleNode',\n 'SocialNode',\n 'SocialOAuthIgnoreProfileNode',\n 'SocialOpenIdConnectNode',\n 'TimerStartNode',\n 'TimerStopNode',\n 'UsernameCollectorNode',\n 'WebAuthnAuthenticationNode',\n 'WebAuthnRegistrationNode',\n 'ZeroPageLoginNode',\n];\n\n/**\n * Analyze if a journey contains any custom nodes considering the detected or the overridden version.\n * @param {Object} journey Journey/tree configuration object\n * @returns {boolean} True if the journey/tree contains any custom nodes, false otherwise.\n */\nexport async function isCustom(journey) {\n let ootbNodeTypes = [];\n const nodeList = journey.nodes;\n switch (storage.session.getAmVersion()) {\n case '7.1.0':\n ootbNodeTypes = OOTB_NODE_TYPES_7_1.slice(0);\n break;\n case '7.2.0':\n ootbNodeTypes = OOTB_NODE_TYPES_7_2.slice(0);\n break;\n case '7.0.0':\n case '7.0.1':\n case '7.0.2':\n ootbNodeTypes = OOTB_NODE_TYPES_7.slice(0);\n break;\n case '6.5.3':\n case '6.5.2.3':\n case '6.5.2.2':\n case '6.5.2.1':\n case '6.5.2':\n case '6.5.1':\n case '6.5.0.2':\n case '6.5.0.1':\n ootbNodeTypes = OOTB_NODE_TYPES_6_5.slice(0);\n break;\n case '6.0.0.7':\n case '6.0.0.6':\n case '6.0.0.5':\n case '6.0.0.4':\n case '6.0.0.3':\n case '6.0.0.2':\n case '6.0.0.1':\n case '6.0.0':\n ootbNodeTypes = OOTB_NODE_TYPES_6.slice(0);\n break;\n default:\n return true;\n }\n const results = [];\n for (const node in nodeList) {\n if ({}.hasOwnProperty.call(nodeList, node)) {\n if (!ootbNodeTypes.includes(nodeList[node].nodeType)) {\n return true;\n }\n if (containerNodes.includes(nodeList[node].nodeType)) {\n results.push(await getNode(node, nodeList[node].nodeType));\n }\n }\n }\n const pageNodes = await Promise.all(results);\n let custom = false;\n for (const pageNode of pageNodes) {\n if (pageNode != null) {\n for (const pnode of pageNode.nodes) {\n if (!ootbNodeTypes.includes(pnode.nodeType)) {\n custom = true;\n }\n }\n } else {\n printMessage(\n `isCustom ERROR: can't get ${nodeList[pageNode].nodeType} with id ${pageNode} in ${journey._id}`,\n 'error'\n );\n custom = false;\n }\n }\n return custom;\n}\n\n/**\n * List all the journeys/trees\n * @param {boolean} long Long version, all the fields\n * @param {boolean} analyze Analyze journeys/trees for custom nodes (expensive)\n * @returns {Promise<unknown[]>} a promise that resolves to an array journey objects\n */\nexport async function listJourneys(\n long = false,\n analyze = false\n): Promise<unknown[]> {\n let journeys = [];\n try {\n journeys = (await getTrees()).result;\n } catch (error) {\n printMessage(`${error.message}`, 'error');\n printMessage(error.response.data, 'error');\n }\n journeys.sort((a, b) => a._id.localeCompare(b._id));\n let customTrees = Array(journeys.length).fill(false);\n if (analyze) {\n const results = [];\n for (const journey of journeys) {\n results.push(isCustom(journey));\n }\n customTrees = await Promise.all(results);\n }\n if (!long) {\n for (const [i, journey] of journeys.entries()) {\n printMessage(`${customTrees[i] ? '*' : ''}${journey._id}`, 'data');\n }\n } else {\n const table = createTable([\n 'Name'['brightCyan'],\n 'Status'['brightCyan'],\n 'Tags'['brightCyan'],\n ]);\n for (const [i, journey] of journeys.entries()) {\n table.push([\n `${customTrees[i] ? '*'['brightRed'] : ''}${journey._id}`,\n journey.enabled === false\n ? 'disabled'['brightRed']\n : 'enabled'['brightGreen'],\n journey.uiConfig && journey.uiConfig.categories\n ? wordwrap(JSON.parse(journey.uiConfig.categories).join(', '), 60)\n : '',\n ]);\n }\n printMessage(table.toString(), 'data');\n }\n return journeys;\n}\n\n/**\n * Delete a journey\n * @param {string} journeyId journey id/name\n * @param {Object} options deep=true also delete all the nodes and inner nodes, verbose=true print verbose info\n */\nexport async function deleteJourney(\n journeyId: string,\n options,\n progress = true\n) {\n const { deep } = options;\n const { verbose } = options;\n const status = { nodes: {} };\n if (progress)\n createProgressIndicator(\n undefined,\n `Deleting ${journeyId}...`,\n 'indeterminate'\n );\n if (progress && verbose) stopProgressIndicator();\n return deleteTree(journeyId)\n .then(async (deleteTreeResponse) => {\n status['status'] = 'success';\n const nodePromises = [];\n if (verbose) printMessage(`Deleted ${journeyId} (tree)`, 'info');\n if (deep) {\n for (const [nodeId, nodeObject] of Object.entries(\n deleteTreeResponse.nodes\n )) {\n // delete inner nodes (nodes inside container nodes)\n if (containerNodes.includes(nodeObject['nodeType'])) {\n try {\n // eslint-disable-next-line no-await-in-loop\n const containerNode = await getNode(\n nodeId,\n nodeObject['nodeType']\n );\n if (verbose)\n printMessage(\n `Read ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}`,\n 'info'\n );\n for (const innerNodeObject of containerNode.nodes) {\n nodePromises.push(\n deleteNode(innerNodeObject._id, innerNodeObject.nodeType)\n .then((response2) => {\n status.nodes[innerNodeObject._id] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${innerNodeObject._id} (${innerNodeObject.nodeType}) from ${journeyId}`,\n 'info'\n );\n return response2;\n })\n .catch((error) => {\n status.nodes[innerNodeObject._id] = {\n status: 'error',\n error,\n };\n if (verbose)\n printMessage(\n `Error deleting inner node ${innerNodeObject._id} (${innerNodeObject.nodeType}) from ${journeyId}: ${error}`,\n 'error'\n );\n })\n );\n }\n // finally delete the container node\n nodePromises.push(\n deleteNode(containerNode._id, containerNode['_type']['_id'])\n .then((response2) => {\n status.nodes[containerNode._id] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${containerNode._id} (${containerNode['_type']['_id']}) from ${journeyId}`,\n 'info'\n );\n return response2;\n })\n .catch((error) => {\n if (\n error?.response?.data?.code === 500 &&\n error.response.data.message ===\n 'Unable to read SMS config: Node did not exist'\n ) {\n status.nodes[containerNode._id] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${containerNode._id} (${containerNode['_type']['_id']}) from ${journeyId}`,\n 'info'\n );\n } else {\n status.nodes[containerNode._id] = {\n status: 'error',\n error,\n };\n if (verbose)\n printMessage(\n `Error deleting container node ${containerNode._id} (${containerNode['_type']['_id']}) from ${journeyId}: ${error.response.data.message}`,\n 'error'\n );\n }\n })\n );\n } catch (error) {\n if (verbose)\n printMessage(\n `Error getting container node ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}: ${error}`,\n 'error'\n );\n }\n } else {\n // delete the node\n nodePromises.push(\n deleteNode(nodeId, nodeObject['nodeType'])\n .then((response) => {\n status.nodes[nodeId] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}`,\n 'info'\n );\n return response;\n })\n .catch((error) => {\n status.nodes[nodeId] = { status: 'error', error };\n if (verbose)\n printMessage(\n `Error deleting node ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}: ${error}`,\n 'error'\n );\n })\n );\n }\n }\n }\n // wait until all the node calls are complete\n await Promise.allSettled(nodePromises);\n\n // report status\n if (progress) {\n let nodeCount = 0;\n let errorCount = 0;\n for (const node of Object.keys(status.nodes)) {\n nodeCount += 1;\n if (status.nodes[node].status === 'error') errorCount += 1;\n }\n if (errorCount === 0) {\n stopProgressIndicator(\n `Deleted ${journeyId} and ${\n nodeCount - errorCount\n }/${nodeCount} nodes.`,\n 'success'\n );\n } else {\n stopProgressIndicator(\n `Deleted ${journeyId} and ${\n nodeCount - errorCount\n }/${nodeCount} nodes.`,\n 'fail'\n );\n }\n }\n return status;\n })\n .catch((error) => {\n status['status'] = 'error';\n status['error'] = error;\n stopProgressIndicator(`Error deleting ${journeyId}.`, 'fail');\n if (verbose)\n printMessage(`Error deleting tree ${journeyId}: ${error}`, 'error');\n return status;\n });\n}\n\n/**\n * Delete all journeys\n * @param {Object} options deep=true also delete all the nodes and inner nodes, verbose=true print verbose info\n */\nexport async function deleteJourneys(options) {\n const { verbose } = options;\n const status = {};\n const trees = (await getTrees()).result;\n createProgressIndicator(trees.length, 'Deleting journeys...');\n for (const tree of trees) {\n if (verbose) printMessage('');\n // eslint-disable-next-line no-await-in-loop\n status[tree._id] = await deleteJourney(tree._id, options, false);\n updateProgressIndicator(`${tree._id}`);\n // introduce a 100ms wait to allow the progress bar to update before the next verbose message prints from the async function\n if (verbose)\n // eslint-disable-next-line no-await-in-loop\n await new Promise((r) => {\n setTimeout(r, 100);\n });\n }\n let journeyCount = 0;\n let journeyErrorCount = 0;\n let nodeCount = 0;\n let nodeErrorCount = 0;\n for (const journey of Object.keys(status)) {\n journeyCount += 1;\n if (status[journey].status === 'error') journeyErrorCount += 1;\n for (const node of Object.keys(status[journey].nodes)) {\n nodeCount += 1;\n if (status[journey].nodes[node].status === 'error') nodeErrorCount += 1;\n }\n }\n stopProgressIndicator(\n `Deleted ${journeyCount - journeyErrorCount}/${journeyCount} journeys and ${\n nodeCount - nodeErrorCount\n }/${nodeCount} nodes.`\n );\n return status;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/ops/JourneyOps.ts"],"names":[],"mappings":"AA0EA,UAAU,2BAA2B;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAGD,wBAAgB,iCAAiC,IAAI,2BAA2B,CAa/E;AAED,UAAU,0BAA0B;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;CACpD;AAGD,wBAAgB,gCAAgC,IAAI,0BAA0B,CAK7E;AAqGD;;GAEG;AACH,UAAU,aAAa;IACrB;;OAEG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IACd;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAIR,GACA,OAAO,CAAC,2BAA2B,CAAC,CAsWtC;AAED;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAUtD;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;GAEG;AACH,UAAU,aAAa;IACrB;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IACd;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,2BAA2B,EACvC,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAqaf;AA6ED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,iBA6EvB;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,iBAkFvB;AAkDD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,iBAOvB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,aAAa,iBAcnE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,2BAA2B,GACvC,IAAI,CAqFN;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAgF5D;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,OAAO,EAAE,GACvB,OAAO,CAAC,OAAO,EAAE,CAAC,CAepB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,2BAA2B,WAUnE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,WAUpE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,2BAA2B,WAUtE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,2BAA2B,GACnC,MAAM,EAAE,CAcV;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,UAAQ,EACZ,OAAO,UAAQ,GACd,OAAO,CAAC,OAAO,EAAE,CAAC,CA+DpB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,KAAA,EACP,QAAQ,UAAO;;GAqKhB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,KAAA,eAmC3C","file":"JourneyOps.d.ts","sourcesContent":["import fs from 'fs';\nimport { v4 as uuidv4 } from 'uuid';\nimport _ from 'lodash';\nimport {\n convertBase64TextToArray,\n getTypedFilename,\n saveJsonToFile,\n getRealmString,\n convertTextArrayToBase64,\n convertTextArrayToBase64Url,\n} from './utils/ExportImportUtils';\nimport { getRealmManagedUser, replaceAll } from './utils/OpsUtils';\nimport storage from '../storage/SessionStorage';\nimport {\n getNode,\n putNode,\n deleteNode,\n getNodeTypes,\n getNodesByType,\n} from '../api/NodeApi';\nimport { isCloudOnlyNode, isCustomNode, isPremiumNode } from './NodeOps';\nimport { getTrees, getTree, putTree, deleteTree } from '../api/TreeApi';\nimport { getEmailTemplate, putEmailTemplate } from '../api/EmailTemplateApi';\nimport { getScript } from '../api/ScriptApi';\nimport * as global from '../storage/StaticStorage';\nimport {\n printMessage,\n createProgressIndicator,\n updateProgressIndicator,\n stopProgressIndicator,\n createTable,\n createKeyValueTable,\n} from './utils/Console';\nimport wordwrap from './utils/Wordwrap';\nimport {\n getProviderByLocationAndId,\n getProviders,\n getProviderMetadata,\n createProvider,\n findProviders,\n updateProvider,\n} from '../api/Saml2Api';\nimport {\n createCircleOfTrust,\n getCirclesOfTrust,\n updateCircleOfTrust,\n} from '../api/CirclesOfTrustApi';\nimport {\n decode,\n encode,\n encodeBase64Url,\n isBase64Encoded,\n} from '../api/utils/Base64';\nimport {\n getSocialIdentityProviders,\n putProviderByTypeAndId,\n} from '../api/SocialIdentityProvidersApi';\nimport { getThemes, putThemes } from '../api/ThemeApi';\nimport { createOrUpdateScript } from './ScriptOps';\n\nconst containerNodes = ['PageNode', 'CustomPageNode'];\n\nconst scriptedNodes = [\n 'ConfigProviderNode',\n 'ScriptedDecisionNode',\n 'ClientScriptNode',\n 'SocialProviderHandlerNode',\n 'CustomScriptNode',\n];\n\nconst emailTemplateNodes = ['EmailSuspendNode', 'EmailTemplateNode'];\n\nconst emptyScriptPlaceholder = '[Empty]';\n\ninterface SingleJourneyExportTemplate {\n meta?: Record<string, unknown>;\n innerNodes?: Record<string, unknown>;\n innernodes?: Record<string, unknown>;\n nodes?: Record<string, unknown>;\n scripts?: Record<string, unknown>;\n emailTemplates?: Record<string, unknown>;\n socialIdentityProviders?: Record<string, unknown>;\n themes?: unknown[];\n saml2Entities?: Record<string, unknown>;\n circlesOfTrust?: Record<string, unknown>;\n tree: Record<string, unknown>;\n}\n\n// use a function vs a template variable to avoid problems in loops\nexport function createSingleJourneyExportTemplate(): SingleJourneyExportTemplate {\n return {\n meta: {},\n innerNodes: {},\n nodes: {},\n scripts: {},\n emailTemplates: {},\n socialIdentityProviders: {},\n themes: [],\n saml2Entities: {},\n circlesOfTrust: {},\n tree: {},\n } as SingleJourneyExportTemplate;\n}\n\ninterface MultiJourneyExportTemplate {\n meta?: Record<string, unknown>;\n trees: Record<string, SingleJourneyExportTemplate>;\n}\n\n// use a function vs a template variable to avoid problems in loops\nexport function createMultiJourneyExportTemplate(): MultiJourneyExportTemplate {\n return {\n meta: {},\n trees: {},\n } as MultiJourneyExportTemplate;\n}\n\n/**\n * Helper to get all SAML2 dependencies for a given node object\n * @param {Object} nodeObject node object\n * @param {[Object]} allProviders array of all saml2 providers objects\n * @param {[Object]} allCirclesOfTrust array of all circle of trust objects\n * @returns {Promise} a promise that resolves to an object containing a saml2 dependencies\n */\nasync function getSaml2NodeDependencies(\n nodeObject,\n allProviders,\n allCirclesOfTrust\n) {\n const samlProperties = ['metaAlias', 'idpEntityId'];\n const saml2EntityPromises = [];\n for (const samlProperty of samlProperties) {\n // In the following line nodeObject[samlProperty] will look like '/alpha/iSPAzure'.\n const entityId =\n samlProperty === 'metaAlias'\n ? _.last(nodeObject[samlProperty].split('/'))\n : nodeObject[samlProperty];\n const entity = _.find(allProviders, { entityId });\n if (entity) {\n try {\n const providerResponse = await getProviderByLocationAndId(\n entity.location,\n entity._id\n );\n /**\n * Adding entityLocation here to the entityResponse because the import tool\n * needs to know whether the saml2 entity is remote or not (this will be removed\n * from the config before importing see updateSaml2Entity and createSaml2Entity functions).\n * Importing a remote saml2 entity is a slightly different request (see createSaml2Entity).\n */\n providerResponse.entityLocation = entity.location;\n\n if (entity.location === 'remote') {\n // get the xml representation of this entity and add it to the entityResponse;\n const metaDataResponse = await getProviderMetadata(\n providerResponse.entityId\n );\n providerResponse.base64EntityXML = encodeBase64Url(metaDataResponse);\n }\n saml2EntityPromises.push(providerResponse);\n } catch (error) {\n printMessage(error.message, 'error');\n }\n }\n }\n try {\n const saml2EntitiesPromisesResults = await Promise.all(saml2EntityPromises);\n const saml2Entities = [];\n for (const saml2Entity of saml2EntitiesPromisesResults) {\n if (saml2Entity) {\n saml2Entities.push(saml2Entity);\n }\n }\n const samlEntityIds = _.map(\n saml2Entities,\n (saml2EntityConfig) => `${saml2EntityConfig.entityId}|saml2`\n );\n const circlesOfTrust = _.filter(allCirclesOfTrust, (circleOfTrust) => {\n let hasEntityId = false;\n for (const trustedProvider of circleOfTrust.trustedProviders) {\n if (!hasEntityId && samlEntityIds.includes(trustedProvider)) {\n hasEntityId = true;\n }\n }\n return hasEntityId;\n });\n const saml2NodeDependencies = {\n saml2Entities,\n circlesOfTrust,\n };\n return saml2NodeDependencies;\n } catch (error) {\n printMessage(error.message, 'error');\n const saml2NodeDependencies = {\n saml2Entities: [],\n circlesOfTrust: [],\n };\n return saml2NodeDependencies;\n }\n}\n\n// export async function getTreeNodes(treeObject) {\n// const nodeList = Object.entries(treeObject.nodes);\n// const results = await Promise.allSettled(\n// nodeList.map(\n// async ([nodeId, nodeInfo]) => await getNode(nodeId, nodeInfo['nodeType'])\n// )\n// );\n// const nodes = results.filter((r) => r.status === 'fulfilled');\n// nodes.map((f) => {\n// return f.status;\n// });\n// const failedList = results.filter((r) => r.status === 'rejected');\n// return nodes;\n// }\n\n/**\n * Export options\n */\ninterface ExportOptions {\n /**\n * Where applicable, use string arrays to store multi-line text (e.g. scripts).\n */\n useStringArrays: boolean;\n /**\n * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes).\n */\n deps: boolean;\n /**\n * Verbose output during command execution. May or may not produce additional output.\n */\n verbose: boolean;\n}\n\n/**\n * Create export data for a tree/journey with all its nodes and dependencies. The export data can be written to a file as is.\n * @param {string} treeId tree id/name\n * @param {ExportOptions} options export options\n * @returns {Promise<SingleJourneyExportTemplate>} a promise that resolves to an object containing the tree and all its nodes and dependencies\n */\nexport async function exportJourney(\n treeId: string,\n options: ExportOptions = {\n useStringArrays: true,\n deps: true,\n verbose: false,\n }\n): Promise<SingleJourneyExportTemplate> {\n const treeObject = await getTree(treeId);\n const exportData = createSingleJourneyExportTemplate();\n const { useStringArrays, deps, verbose } = options;\n\n if (verbose) printMessage(`\\n- ${treeObject._id}\\n`, 'info', false);\n\n // Process tree\n if (verbose) printMessage(' - Flow');\n exportData.tree = treeObject;\n if (verbose && treeObject.identityResource)\n printMessage(\n ` - identityResource: ${treeObject.identityResource}`,\n 'info'\n );\n if (verbose) printMessage(` - Done`, 'info');\n\n const nodePromises = [];\n const scriptPromises = [];\n const emailTemplatePromises = [];\n const innerNodePromises = [];\n const saml2ConfigPromises = [];\n let socialProviderPromise = null;\n const themePromise =\n deps &&\n storage.session.getDeploymentType() !== global.CLASSIC_DEPLOYMENT_TYPE_KEY\n ? getThemes().catch((error) => {\n printMessage(error, 'error');\n })\n : null;\n\n let allSaml2Providers = null;\n let allCirclesOfTrust = null;\n let filteredSocialProviders = null;\n const themes = [];\n\n // get all the nodes\n for (const [nodeId, nodeInfo] of Object.entries(treeObject.nodes)) {\n nodePromises.push(getNode(nodeId, nodeInfo['nodeType']));\n }\n if (verbose && nodePromises.length > 0) printMessage(' - Nodes:');\n const nodeObjects = await Promise.all(nodePromises);\n\n // iterate over every node in tree\n for (const nodeObject of nodeObjects) {\n const nodeId = nodeObject._id;\n const nodeType = nodeObject._type._id;\n if (verbose) printMessage(` - ${nodeId} (${nodeType})`, 'info', true);\n exportData.nodes[nodeObject._id] = nodeObject;\n\n // handle script node types\n if (\n deps &&\n scriptedNodes.includes(nodeType) &&\n nodeObject.script !== emptyScriptPlaceholder\n ) {\n scriptPromises.push(getScript(nodeObject.script));\n }\n\n // frodo supports email templates in platform deployments\n if (\n (deps &&\n storage.session.getDeploymentType() ===\n global.CLOUD_DEPLOYMENT_TYPE_KEY) ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY\n ) {\n if (emailTemplateNodes.includes(nodeType)) {\n try {\n const emailTemplate = await getEmailTemplate(\n nodeObject.emailTemplateName\n );\n emailTemplatePromises.push(emailTemplate);\n } catch (error) {\n let message = `${error}`;\n if (error.isAxiosError && error.response.status) {\n message = error.response.statusText;\n }\n printMessage(\n `\\n${message}: Email Template \"${nodeObject.emailTemplateName}\"`,\n 'error'\n );\n }\n }\n }\n\n // handle SAML2 node dependencies\n if (deps && nodeType === 'product-Saml2Node') {\n if (!allSaml2Providers) {\n // eslint-disable-next-line no-await-in-loop\n allSaml2Providers = (await getProviders()).result;\n }\n if (!allCirclesOfTrust) {\n // eslint-disable-next-line no-await-in-loop\n allCirclesOfTrust = (await getCirclesOfTrust()).result;\n }\n saml2ConfigPromises.push(\n getSaml2NodeDependencies(\n nodeObject,\n allSaml2Providers,\n allCirclesOfTrust\n )\n );\n }\n\n // If this is a SocialProviderHandlerNode get each enabled social identity provider.\n if (\n deps &&\n !socialProviderPromise &&\n nodeType === 'SocialProviderHandlerNode'\n ) {\n socialProviderPromise = getSocialIdentityProviders();\n }\n\n // If this is a SelectIdPNode and filteredProviters is not already set to empty array set filteredSocialProviers.\n if (deps && !filteredSocialProviders && nodeType === 'SelectIdPNode') {\n filteredSocialProviders = filteredSocialProviders || [];\n for (const filteredProvider of nodeObject.filteredProviders) {\n if (!filteredSocialProviders.includes(filteredProvider)) {\n filteredSocialProviders.push(filteredProvider);\n }\n }\n }\n\n // get inner nodes (nodes inside container nodes)\n if (containerNodes.includes(nodeType)) {\n for (const innerNode of nodeObject.nodes) {\n innerNodePromises.push(getNode(innerNode._id, innerNode.nodeType));\n }\n // frodo supports themes in platform deployments\n if (\n (deps &&\n storage.session.getDeploymentType() ===\n global.CLOUD_DEPLOYMENT_TYPE_KEY) ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY\n ) {\n let themeId = false;\n\n if (nodeObject.stage) {\n // see if themeId is part of the stage object\n try {\n themeId = JSON.parse(nodeObject.stage).themeId;\n } catch (e) {\n themeId = false;\n }\n // if the page node's themeId is set the \"old way\" set themeId accordingly\n if (!themeId && nodeObject.stage.indexOf('themeId=') === 0) {\n // eslint-disable-next-line prefer-destructuring\n themeId = nodeObject.stage.split('=')[1];\n }\n }\n\n if (themeId) {\n if (!themes.includes(themeId)) themes.push(themeId);\n }\n }\n }\n }\n\n // Process inner nodes\n if (verbose && innerNodePromises.length > 0) printMessage(' - Inner nodes:');\n const innerNodeDataResults = await Promise.all(innerNodePromises);\n for (const innerNodeObject of innerNodeDataResults) {\n const innerNodeId = innerNodeObject._id;\n const innerNodeType = innerNodeObject._type._id;\n if (verbose)\n printMessage(` - ${innerNodeId} (${innerNodeType})`, 'info', true);\n exportData.innerNodes[innerNodeId] = innerNodeObject;\n\n // handle script node types\n if (deps && scriptedNodes.includes(innerNodeType)) {\n scriptPromises.push(getScript(innerNodeObject.script));\n }\n\n // frodo supports email templates in platform deployments\n if (\n (deps &&\n storage.session.getDeploymentType() ===\n global.CLOUD_DEPLOYMENT_TYPE_KEY) ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY\n ) {\n if (emailTemplateNodes.includes(innerNodeType)) {\n try {\n const emailTemplate = await getEmailTemplate(\n innerNodeObject.emailTemplateName\n );\n emailTemplatePromises.push(emailTemplate);\n } catch (error) {\n let message = `${error}`;\n if (error.isAxiosError && error.response.status) {\n message = error.response.statusText;\n }\n printMessage(\n `\\n${message}: Email Template \"${innerNodeObject.emailTemplateName}\"`,\n 'error'\n );\n }\n }\n }\n\n // handle SAML2 node dependencies\n if (deps && innerNodeType === 'product-Saml2Node') {\n printMessage('SAML2 inner node', 'error');\n if (!allSaml2Providers) {\n // eslint-disable-next-line no-await-in-loop\n allSaml2Providers = (await getProviders()).result;\n }\n if (!allCirclesOfTrust) {\n // eslint-disable-next-line no-await-in-loop\n allCirclesOfTrust = (await getCirclesOfTrust()).result;\n }\n saml2ConfigPromises.push(\n getSaml2NodeDependencies(\n innerNodeObject,\n allSaml2Providers,\n allCirclesOfTrust\n )\n );\n }\n\n // If this is a SocialProviderHandlerNode get each enabled social identity provider.\n if (\n deps &&\n !socialProviderPromise &&\n innerNodeType === 'SocialProviderHandlerNode'\n ) {\n socialProviderPromise = getSocialIdentityProviders();\n }\n\n // If this is a SelectIdPNode and filteredProviters is not already set to empty array set filteredSocialProviers.\n if (\n deps &&\n !filteredSocialProviders &&\n innerNodeType === 'SelectIdPNode' &&\n innerNodeObject.filteredProviders\n ) {\n filteredSocialProviders = filteredSocialProviders || [];\n for (const filteredProvider of innerNodeObject.filteredProviders) {\n if (!filteredSocialProviders.includes(filteredProvider)) {\n filteredSocialProviders.push(filteredProvider);\n }\n }\n }\n }\n\n // Process email templates\n if (verbose && emailTemplatePromises.length > 0)\n printMessage(' - Email templates:');\n const settledEmailTemplatePromises = await Promise.allSettled(\n emailTemplatePromises\n );\n for (const settledPromise of settledEmailTemplatePromises) {\n if (settledPromise.status === 'fulfilled' && settledPromise.value) {\n if (verbose)\n printMessage(\n ` - ${settledPromise.value._id.split('/')[1]}${\n settledPromise.value.displayName\n ? ` (${settledPromise.value.displayName})`\n : ''\n }`,\n 'info',\n true\n );\n exportData.emailTemplates[settledPromise.value._id.split('/')[1]] =\n settledPromise.value;\n }\n }\n\n // Process SAML2 providers and circles of trust\n const saml2NodeDependencies = await Promise.all(saml2ConfigPromises);\n for (const saml2NodeDependency of saml2NodeDependencies) {\n if (saml2NodeDependency) {\n if (verbose) printMessage(' - SAML2 entity providers:');\n for (const saml2Entity of saml2NodeDependency.saml2Entities) {\n if (verbose)\n printMessage(\n ` - ${saml2Entity.entityLocation} ${saml2Entity.entityId}`,\n 'info'\n );\n exportData.saml2Entities[saml2Entity._id] = saml2Entity;\n }\n if (verbose) printMessage(' - SAML2 circles of trust:');\n for (const circleOfTrust of saml2NodeDependency.circlesOfTrust) {\n if (verbose) printMessage(` - ${circleOfTrust._id}`, 'info');\n exportData.circlesOfTrust[circleOfTrust._id] = circleOfTrust;\n }\n }\n }\n\n // Process socialIdentityProviders\n const socialProviders = await Promise.resolve(socialProviderPromise);\n if (socialProviders) {\n if (verbose) printMessage(' - OAuth2/OIDC (social) identity providers:');\n for (const socialProvider of socialProviders.result) {\n // If the list of socialIdentityProviders needs to be filtered based on the\n // filteredProviders property of a SelectIdPNode do it here.\n if (\n socialProvider &&\n (!filteredSocialProviders ||\n filteredSocialProviders.length === 0 ||\n filteredSocialProviders.includes(socialProvider._id))\n ) {\n if (verbose) printMessage(` - ${socialProvider._id}`, 'info');\n scriptPromises.push(getScript(socialProvider.transform));\n exportData.socialIdentityProviders[socialProvider._id] = socialProvider;\n }\n }\n }\n\n // Process scripts\n if (verbose && scriptPromises.length > 0) printMessage(' - Scripts:');\n const scriptObjects = await Promise.all(scriptPromises);\n for (const scriptObject of scriptObjects) {\n if (scriptObject) {\n if (verbose)\n printMessage(\n ` - ${scriptObject._id} (${scriptObject.name})`,\n 'info',\n true\n );\n scriptObject.script = useStringArrays\n ? convertBase64TextToArray(scriptObject.script)\n : JSON.stringify(decode(scriptObject.script));\n exportData.scripts[scriptObject._id] = scriptObject;\n }\n }\n\n // Process themes\n if (themePromise) {\n if (verbose) printMessage(' - Themes:');\n try {\n const themePromiseResults = await Promise.resolve(themePromise);\n for (const themeObject of themePromiseResults) {\n if (\n themeObject &&\n // has the theme been specified by id or name in a page node?\n (themes.includes(themeObject._id) ||\n themes.includes(themeObject.name) ||\n // has this journey been linked to a theme?\n themeObject.linkedTrees?.includes(treeObject._id))\n ) {\n if (verbose)\n printMessage(\n ` - ${themeObject._id} (${themeObject.name})`,\n 'info'\n );\n exportData.themes.push(themeObject);\n }\n }\n } catch (error) {\n printMessage(error, 'error');\n printMessage('Error handling themes: ' + error.message, 'error');\n }\n }\n\n return exportData;\n}\n\n/**\n * Get all the journeys/trees without all their nodes and dependencies.\n * @returns {Promise<unknown[]>} a promise that resolves to an array of journey objects\n */\nexport async function getJourneys(): Promise<unknown[]> {\n let journeys = [];\n try {\n journeys = (await getTrees()).result;\n } catch (error) {\n printMessage(`${error.message}`, 'error');\n printMessage(error.response.data, 'error');\n }\n journeys.sort((a, b) => a._id.localeCompare(b._id));\n return journeys;\n}\n\n/**\n * Export journey by id/name to file\n * @param {string} journeyId journey id/name\n * @param {string} file optional export file name\n * @param {ExportOptions} options export options\n */\nexport async function exportJourneyToFile(\n journeyId: string,\n file: string,\n options: ExportOptions\n): Promise<void> {\n const { verbose } = options;\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(journeyId, 'journey');\n }\n if (!verbose)\n createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');\n try {\n const fileData: SingleJourneyExportTemplate = await exportJourney(\n journeyId,\n options\n );\n if (verbose)\n createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `Exported ${journeyId['brightCyan']} to ${fileName['brightCyan']}.`,\n 'success'\n );\n } catch (error) {\n if (verbose)\n createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');\n stopProgressIndicator(\n `Error exporting journey ${journeyId}: ${error}`,\n 'fail'\n );\n }\n}\n\n/**\n * Export all journeys to file\n * @param {string} file optional export file name\n * @param {ExportOptions} options export options\n */\nexport async function exportJourneysToFile(\n file: string,\n options: ExportOptions\n): Promise<void> {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(`all${getRealmString()}Journeys`, 'journeys');\n }\n const trees = (await getTrees()).result;\n const fileData: MultiJourneyExportTemplate =\n createMultiJourneyExportTemplate();\n createProgressIndicator(trees.length, 'Exporting journeys...');\n for (const tree of trees) {\n updateProgressIndicator(`${tree._id}`);\n try {\n const exportData = await exportJourney(tree._id, options);\n delete exportData.meta;\n fileData.trees[tree._id] = exportData;\n } catch (error) {\n printMessage(`Error exporting journey ${tree._id}: ${error}`, 'error');\n }\n }\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(`Exported to ${fileName}`);\n}\n\n/**\n * Export all journeys to separate files\n * @param {ExportOptions} options export options\n */\nexport async function exportJourneysToFiles(\n options: ExportOptions\n): Promise<void> {\n const trees = (await getTrees()).result;\n createProgressIndicator(trees.length, 'Exporting journeys...');\n for (const tree of trees) {\n updateProgressIndicator(`${tree._id}`);\n const fileName = getTypedFilename(`${tree._id}`, 'journey');\n try {\n const exportData: SingleJourneyExportTemplate = await exportJourney(\n tree._id,\n options\n );\n saveJsonToFile(exportData, fileName);\n } catch (error) {\n // do we need to report status here?\n }\n }\n stopProgressIndicator('Done');\n}\n\n/**\n * Import options\n */\ninterface ImportOptions {\n /**\n * Generate new UUIDs for all nodes during import.\n */\n reUuid: boolean;\n /**\n * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes).\n */\n deps: boolean;\n /**\n * Verbose output during command execution. May or may not produce additional output.\n */\n verbose: boolean;\n}\n\n/**\n * Helper to import a tree with all dependencies from an import data object (typically read from a file)\n * @param {SingleJourneyExportTemplate} treeObject tree object containing tree and all its dependencies\n * @param {ImportOptions} options import options\n */\nexport async function importJourney(\n treeObject: SingleJourneyExportTemplate,\n options: ImportOptions\n): Promise<void> {\n const { reUuid, deps, verbose } = options;\n if (verbose) printMessage(`\\n- ${treeObject.tree._id}\\n`, 'info', false);\n let newUuid = '';\n const uuidMap = {};\n const treeId = treeObject.tree._id;\n\n // Process scripts\n if (\n deps &&\n treeObject.scripts &&\n Object.entries(treeObject.scripts).length > 0\n ) {\n if (verbose) printMessage(' - Scripts:');\n for (const [scriptId, scriptObject] of Object.entries(treeObject.scripts)) {\n if (verbose)\n printMessage(\n ` - ${scriptId} (${scriptObject['name']})`,\n 'info',\n false\n );\n // is the script stored as an array of strings or just b64 blob?\n if (Array.isArray(scriptObject['script'])) {\n scriptObject['script'] = convertTextArrayToBase64(\n scriptObject['script']\n );\n } else if (!isBase64Encoded(scriptObject['script'])) {\n scriptObject['script'] = encode(JSON.parse(scriptObject['script']));\n }\n if ((await createOrUpdateScript(scriptId, scriptObject)) == null) {\n throw new Error(\n `Error importing script ${scriptObject['name']} (${scriptId}) in journey ${treeId}`\n );\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process email templates\n if (\n deps &&\n treeObject.emailTemplates &&\n Object.entries(treeObject.emailTemplates).length > 0\n ) {\n if (verbose) printMessage(' - Email templates:');\n for (const [templateId, templateData] of Object.entries(\n treeObject.emailTemplates\n )) {\n if (verbose) printMessage(` - ${templateId}`, 'info', false);\n try {\n await putEmailTemplate(templateId, templateData);\n } catch (error) {\n printMessage(error.response.data, 'error');\n throw new Error(`Error importing email templates: ${error.message}`);\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process themes\n if (deps && treeObject.themes && treeObject.themes.length > 0) {\n if (verbose) printMessage(' - Themes:');\n const themes = {};\n for (const theme of treeObject.themes) {\n if (verbose)\n printMessage(` - ${theme['_id']} (${theme['name']})`, 'info');\n themes[theme['_id']] = theme;\n }\n try {\n await putThemes(themes);\n } catch (error) {\n throw new Error(`Error importing themes: ${error.message}`);\n }\n }\n\n // Process social providers\n if (\n deps &&\n treeObject.socialIdentityProviders &&\n Object.entries(treeObject.socialIdentityProviders).length > 0\n ) {\n if (verbose) printMessage(' - OAuth2/OIDC (social) identity providers:');\n for (const [providerId, providerData] of Object.entries(\n treeObject.socialIdentityProviders\n )) {\n if (verbose) printMessage(` - ${providerId}`, 'info');\n try {\n await putProviderByTypeAndId(\n providerData['_type']['_id'],\n providerId,\n providerData\n );\n } catch (importError) {\n if (\n importError.response?.status === 500 &&\n importError.response?.data?.message ===\n 'Unable to update SMS config: Data validation failed for the attribute, Redirect after form post URL'\n ) {\n providerData['redirectAfterFormPostURI'] = '';\n try {\n await putProviderByTypeAndId(\n providerData['_type']['_id'],\n providerId,\n providerData\n );\n } catch (importError2) {\n printMessage(importError.response?.data || importError, 'error');\n throw new Error(\n `Error importing provider ${providerId} in journey ${treeId}: ${importError}`\n );\n }\n } else {\n printMessage(importError.response?.data || importError, 'error');\n throw new Error(\n `\\nError importing provider ${providerId} in journey ${treeId}: ${importError}`\n );\n }\n }\n }\n }\n\n // Process saml providers\n if (\n deps &&\n treeObject.saml2Entities &&\n Object.entries(treeObject.saml2Entities).length > 0\n ) {\n if (verbose) printMessage(' - SAML2 entity providers:');\n for (const [, providerData] of Object.entries(treeObject.saml2Entities)) {\n delete providerData['_rev'];\n const entityId = providerData['entityId'];\n const entityLocation = providerData['entityLocation'];\n if (verbose) printMessage(` - ${entityLocation} ${entityId}`, 'info');\n let metaData = null;\n if (entityLocation === 'remote') {\n if (Array.isArray(providerData['base64EntityXML'])) {\n metaData = convertTextArrayToBase64Url(\n providerData['base64EntityXML']\n );\n } else {\n metaData = providerData['base64EntityXML'];\n }\n }\n delete providerData['entityLocation'];\n delete providerData['base64EntityXML'];\n // create the provider if it doesn't already exist, or just update it\n if (\n (await findProviders(`entityId eq '${entityId}'`, 'location'))\n .resultCount === 0\n ) {\n await createProvider(entityLocation, providerData, metaData).catch(\n (createProviderErr) => {\n printMessage(\n createProviderErr.response?.data || createProviderErr,\n 'error'\n );\n throw new Error(`Error creating provider ${entityId}`);\n }\n );\n } else {\n await updateProvider(entityLocation, providerData).catch(\n (updateProviderErr) => {\n printMessage(\n updateProviderErr.response?.data || updateProviderErr,\n 'error'\n );\n throw new Error(`Error updating provider ${entityId}`);\n }\n );\n }\n }\n }\n\n // Process circles of trust\n if (\n deps &&\n treeObject.circlesOfTrust &&\n Object.entries(treeObject.circlesOfTrust).length > 0\n ) {\n if (verbose) printMessage(' - SAML2 circles of trust:');\n for (const [cotId, cotData] of Object.entries(treeObject.circlesOfTrust)) {\n delete cotData['_rev'];\n if (verbose) printMessage(` - ${cotId}`, 'info');\n try {\n await createCircleOfTrust(cotData);\n } catch (createCotErr) {\n if (\n createCotErr.response?.status === 409 ||\n createCotErr.response?.status === 500\n ) {\n try {\n await updateCircleOfTrust(cotId, cotData);\n } catch (updateCotErr) {\n printMessage(createCotErr.response?.data || createCotErr, 'error');\n printMessage(updateCotErr.response?.data || updateCotErr, 'error');\n throw new Error(`Error creating/updating circle of trust ${cotId}`);\n }\n } else {\n printMessage(createCotErr.response?.data || createCotErr, 'error');\n throw new Error(`Error creating circle of trust ${cotId}`);\n }\n }\n }\n }\n\n // Process inner nodes\n let innerNodes = {};\n if (\n treeObject.innerNodes &&\n Object.entries(treeObject.innerNodes).length > 0\n ) {\n innerNodes = treeObject.innerNodes;\n }\n // old export file format\n else if (\n treeObject.innernodes &&\n Object.entries(treeObject.innernodes).length > 0\n ) {\n innerNodes = treeObject.innernodes;\n }\n if (Object.entries(innerNodes).length > 0) {\n if (verbose) printMessage(' - Inner nodes:', 'text', true);\n for (const [innerNodeId, innerNodeData] of Object.entries(innerNodes)) {\n delete innerNodeData['_rev'];\n const nodeType = innerNodeData['_type']['_id'];\n if (!reUuid) {\n newUuid = innerNodeId;\n } else {\n newUuid = uuidv4();\n uuidMap[innerNodeId] = newUuid;\n }\n innerNodeData['_id'] = newUuid;\n\n if (verbose)\n printMessage(\n ` - ${newUuid}${reUuid ? '*' : ''} (${nodeType})`,\n 'info',\n false\n );\n\n // If the node has an identityResource config setting\n // and the identityResource ends in 'user'\n // and the node's identityResource is the same as the tree's identityResource\n // change it to the current realm managed user identityResource otherwise leave it alone.\n if (\n innerNodeData['identityResource'] &&\n innerNodeData['identityResource'].endsWith('user') &&\n innerNodeData['identityResource'] === treeObject.tree.identityResource\n ) {\n innerNodeData['identityResource'] = `managed/${getRealmManagedUser()}`;\n if (verbose)\n printMessage(\n `\\n - identityResource: ${innerNodeData['identityResource']}`,\n 'info',\n false\n );\n }\n try {\n await putNode(newUuid, nodeType, innerNodeData);\n } catch (nodeImportError) {\n if (\n nodeImportError.response.status === 400 &&\n nodeImportError.response.data.message ===\n 'Data validation failed for the attribute, Script'\n ) {\n throw new Error(\n `Missing script ${\n innerNodeData['script']\n } referenced by inner node ${innerNodeId}${\n innerNodeId === newUuid ? '' : ` [${newUuid}]`\n } (${innerNodeData['_type']['_id']}) in journey ${treeId}.`\n );\n } else {\n printMessage(nodeImportError.response.data, 'error');\n throw new Error(\n `Error importing inner node ${innerNodeId}${\n innerNodeId === newUuid ? '' : ` [${newUuid}]`\n } in journey ${treeId}`\n );\n }\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process nodes\n if (treeObject.nodes && Object.entries(treeObject.nodes).length > 0) {\n if (verbose) printMessage(' - Nodes:');\n // eslint-disable-next-line prefer-const\n for (let [nodeId, nodeData] of Object.entries(treeObject.nodes)) {\n delete nodeData['_rev'];\n const nodeType = nodeData['_type']['_id'];\n if (!reUuid) {\n newUuid = nodeId;\n } else {\n newUuid = uuidv4();\n uuidMap[nodeId] = newUuid;\n }\n nodeData['_id'] = newUuid;\n\n if (nodeType === 'PageNode' && reUuid) {\n for (const [, inPageNodeData] of Object.entries(nodeData['nodes'])) {\n const currentId = inPageNodeData['_id'];\n nodeData = JSON.parse(\n replaceAll(JSON.stringify(nodeData), currentId, uuidMap[currentId])\n );\n }\n }\n\n if (verbose)\n printMessage(\n ` - ${newUuid}${reUuid ? '*' : ''} (${nodeType})`,\n 'info',\n false\n );\n\n // If the node has an identityResource config setting\n // and the identityResource ends in 'user'\n // and the node's identityResource is the same as the tree's identityResource\n // change it to the current realm managed user identityResource otherwise leave it alone.\n if (\n nodeData['identityResource'] &&\n nodeData['identityResource'].endsWith('user') &&\n nodeData['identityResource'] === treeObject.tree.identityResource\n ) {\n nodeData['identityResource'] = `managed/${getRealmManagedUser()}`;\n if (verbose)\n printMessage(\n `\\n - identityResource: ${nodeData['identityResource']}`,\n 'info',\n false\n );\n }\n try {\n await putNode(newUuid, nodeType, nodeData);\n } catch (nodeImportError) {\n if (\n nodeImportError.response.status === 400 &&\n nodeImportError.response.data.message ===\n 'Data validation failed for the attribute, Script'\n ) {\n throw new Error(\n `Missing script ${nodeData['script']} referenced by node ${nodeId}${\n nodeId === newUuid ? '' : ` [${newUuid}]`\n } (${nodeData['_type']['_id']}) in journey ${treeId}.`\n );\n } else {\n printMessage(nodeImportError.response.data, 'error');\n throw new Error(\n `Error importing node ${nodeId}${\n nodeId === newUuid ? '' : ` [${newUuid}]`\n } in journey ${treeId}`\n );\n }\n }\n if (verbose) printMessage('');\n }\n }\n\n // Process tree\n if (verbose) printMessage(' - Flow');\n\n if (reUuid) {\n let journeyText = JSON.stringify(treeObject.tree, null, 2);\n for (const [oldId, newId] of Object.entries(uuidMap)) {\n journeyText = replaceAll(journeyText, oldId, newId);\n }\n treeObject.tree = JSON.parse(journeyText);\n }\n\n // If the tree has an identityResource config setting\n // and the identityResource ends in 'user'\n // Set the identityResource for the tree to the selected resource.\n if (\n treeObject.tree.identityResource &&\n (treeObject.tree['identityResource'] as string).endsWith('user')\n ) {\n treeObject.tree.identityResource = `managed/${getRealmManagedUser()}`;\n if (verbose)\n printMessage(\n ` - identityResource: ${treeObject.tree.identityResource}`,\n 'info',\n false\n );\n }\n\n delete treeObject.tree._rev;\n try {\n await putTree(treeObject.tree._id as string, treeObject.tree);\n if (verbose) printMessage(`\\n - Done`, 'info', true);\n } catch (importError) {\n if (\n importError.response?.status === 400 &&\n importError.response?.data?.message === 'Invalid attribute specified.'\n ) {\n const { validAttributes } = importError.response.data.detail;\n validAttributes.push('_id');\n for (const attribute of Object.keys(treeObject.tree)) {\n if (!validAttributes.includes(attribute)) {\n if (verbose)\n printMessage(\n `\\n - Removing invalid attribute: ${attribute}`,\n 'info',\n false\n );\n delete treeObject.tree[attribute];\n }\n }\n try {\n await putTree(treeObject.tree._id as string, treeObject.tree);\n if (verbose) printMessage(`\\n - Done`, 'info', true);\n } catch (importError2) {\n printMessage(importError2.response.data, 'error');\n throw new Error(`Error importing journey flow ${treeId}`);\n }\n } else {\n printMessage(importError.response?.data || importError, 'error');\n console.dir(importError.response?.data || importError);\n throw new Error(`\\nError importing journey flow ${treeId}`);\n }\n }\n}\n\n/**\n * Resolve journey dependencies\n * @param {Map} installedJorneys Map of installed journeys\n * @param {Map} journeyMap Map of journeys to resolve dependencies for\n * @param {[String]} unresolvedJourneys Map to hold the names of unresolved journeys and their dependencies\n * @param {[String]} resolvedJourneys Array to hold the names of resolved journeys\n * @param {int} index Depth of recursion\n */\nasync function resolveDependencies(\n installedJorneys,\n journeyMap,\n unresolvedJourneys,\n resolvedJourneys,\n index = -1\n) {\n let before = -1;\n let after = index;\n if (index !== -1) {\n before = index;\n }\n\n for (const tree in journeyMap) {\n if ({}.hasOwnProperty.call(journeyMap, tree)) {\n const dependencies = [];\n for (const node in journeyMap[tree].nodes) {\n if (\n journeyMap[tree].nodes[node]._type._id === 'InnerTreeEvaluatorNode'\n ) {\n dependencies.push(journeyMap[tree].nodes[node].tree);\n }\n }\n let allResolved = true;\n for (const dependency of dependencies) {\n if (\n !resolvedJourneys.includes(dependency) &&\n !installedJorneys.includes(dependency)\n ) {\n allResolved = false;\n }\n }\n if (allResolved) {\n if (resolvedJourneys.indexOf(tree) === -1) resolvedJourneys.push(tree);\n // remove from unresolvedJourneys array\n // for (let i = 0; i < unresolvedJourneys.length; i += 1) {\n // if (unresolvedJourneys[i] === tree) {\n // unresolvedJourneys.splice(i, 1);\n // i -= 1;\n // }\n // }\n delete unresolvedJourneys[tree];\n // } else if (!unresolvedJourneys.includes(tree)) {\n } else {\n // unresolvedJourneys.push(tree);\n unresolvedJourneys[tree] = dependencies;\n }\n }\n }\n after = Object.keys(unresolvedJourneys).length;\n if (index !== -1 && after === before) {\n // This is the end, no progress was made since the last recursion\n // printMessage(\n // `Journeys with unresolved dependencies: ${unresolvedJourneys}`,\n // 'error'\n // );\n } else if (after > 0) {\n resolveDependencies(\n installedJorneys,\n journeyMap,\n unresolvedJourneys,\n resolvedJourneys,\n after\n );\n }\n}\n\n/**\n * Import a journey from file\n * @param {string} journeyId journey id/name\n * @param {string} file import file name\n * @param {ImportOptions} options import options\n */\nexport async function importJourneyFromFile(\n journeyId: string,\n file: string,\n options: ImportOptions\n) {\n const { verbose } = options;\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n let journeyData = JSON.parse(data);\n // check if this is a file with multiple trees and get journey by id\n if (journeyData.trees && journeyData.trees[journeyId]) {\n journeyData = journeyData.trees[journeyId];\n } else if (journeyData.trees) {\n journeyData = null;\n }\n\n // if a journeyId was specified, only import the matching journey\n if (journeyData && journeyId === journeyData.tree._id) {\n // attempt dependency resolution for single tree import\n const installedJourneys = (await getTrees()).result.map((x) => x._id);\n const unresolvedJourneys = {};\n const resolvedJourneys = [];\n createProgressIndicator(\n undefined,\n 'Resolving dependencies',\n 'indeterminate'\n );\n await resolveDependencies(\n installedJourneys,\n { [journeyId]: journeyData },\n unresolvedJourneys,\n resolvedJourneys\n );\n if (Object.keys(unresolvedJourneys).length === 0) {\n stopProgressIndicator(`Resolved all dependencies.`, 'success');\n\n if (!verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n importJourney(journeyData, options)\n .then(() => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`Imported ${journeyId}.`, 'success');\n })\n .catch((importError) => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`${importError}`, 'fail');\n });\n } else {\n stopProgressIndicator(`Unresolved dependencies:`, 'fail');\n for (const journey of Object.keys(unresolvedJourneys)) {\n printMessage(\n ` ${journey} requires ${unresolvedJourneys[journey]}`,\n 'error'\n );\n }\n }\n // end dependency resolution for single tree import\n } else {\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`${journeyId} not found!`, 'fail');\n }\n });\n}\n\n/**\n * Import first journey from file\n * @param {string} file import file name\n * @param {ImportOptions} options import options\n */\nexport async function importFirstJourneyFromFile(\n file: string,\n options: ImportOptions\n) {\n const { verbose } = options;\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n let journeyData = _.cloneDeep(JSON.parse(data));\n let journeyId = null;\n // single tree\n if (journeyData.tree) {\n journeyId = _.cloneDeep(journeyData.tree._id);\n }\n // multiple trees, so get the first tree\n else if (journeyData.trees) {\n for (const treeId in journeyData.trees) {\n if (Object.hasOwnProperty.call(journeyData.trees, treeId)) {\n journeyId = treeId;\n journeyData = journeyData.trees[treeId];\n break;\n }\n }\n }\n\n // if a journeyId was specified, only import the matching journey\n if (journeyData && journeyId) {\n // attempt dependency resolution for single tree import\n const installedJourneys = (await getTrees()).result.map((x) => x._id);\n const unresolvedJourneys = {};\n const resolvedJourneys = [];\n createProgressIndicator(\n undefined,\n 'Resolving dependencies',\n 'indeterminate'\n );\n await resolveDependencies(\n installedJourneys,\n { [journeyId]: journeyData },\n unresolvedJourneys,\n resolvedJourneys\n );\n if (Object.keys(unresolvedJourneys).length === 0) {\n stopProgressIndicator(`Resolved all dependencies.`, 'success');\n\n if (!verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n importJourney(journeyData, options)\n .then(() => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`Imported ${journeyId}.`, 'success');\n })\n .catch((importError) => {\n if (verbose)\n createProgressIndicator(\n undefined,\n `Importing ${journeyId}...`,\n 'indeterminate'\n );\n stopProgressIndicator(`${importError}`, 'fail');\n });\n } else {\n stopProgressIndicator(`Unresolved dependencies:`, 'fail');\n for (const journey of Object.keys(unresolvedJourneys)) {\n printMessage(\n ` ${journey} requires ${unresolvedJourneys[journey]}`,\n 'error'\n );\n }\n }\n } else {\n createProgressIndicator(undefined, `Importing...`, 'indeterminate');\n stopProgressIndicator(`No journeys found!`, 'fail');\n }\n // end dependency resolution for single tree import\n });\n}\n\n/**\n * Helper to import multiple trees from a tree map\n * @param {Object} treesMap map of trees object\n * @param {ImportOptions} options import options\n */\nasync function importAllJourneys(\n treesMap: MultiJourneyExportTemplate,\n options: ImportOptions\n) {\n const installedJourneys = (await getTrees()).result.map((x) => x._id);\n const unresolvedJourneys = {};\n const resolvedJourneys = [];\n createProgressIndicator(undefined, 'Resolving dependencies', 'indeterminate');\n await resolveDependencies(\n installedJourneys,\n treesMap,\n unresolvedJourneys,\n resolvedJourneys\n );\n if (Object.keys(unresolvedJourneys).length === 0) {\n stopProgressIndicator(`Resolved all dependencies.`, 'success');\n } else {\n stopProgressIndicator(\n `${\n Object.keys(unresolvedJourneys).length\n } journeys with unresolved dependencies:`,\n 'fail'\n );\n for (const journey of Object.keys(unresolvedJourneys)) {\n printMessage(\n ` - ${journey} requires ${unresolvedJourneys[journey]}`,\n 'info'\n );\n }\n }\n createProgressIndicator(resolvedJourneys.length, 'Importing');\n for (const tree of resolvedJourneys) {\n try {\n // eslint-disable-next-line no-await-in-loop\n await importJourney(treesMap[tree], options);\n updateProgressIndicator(`${tree}`);\n } catch (error) {\n printMessage(`\\n${error.message}`, 'error');\n }\n }\n stopProgressIndicator('Done');\n}\n\n/**\n * Import all journeys from file\n * @param {string} file import file name\n * @param {ImportOptions} options import options\n */\nexport async function importJourneysFromFile(\n file: string,\n options: ImportOptions\n) {\n fs.readFile(file, 'utf8', (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n importAllJourneys(fileData.trees, options);\n });\n}\n\n/**\n * Import all journeys from separate files\n * @param {ImportOptions} options import options\n */\nexport async function importJourneysFromFiles(options: ImportOptions) {\n const names = fs.readdirSync('.');\n const jsonFiles = names.filter((name) =>\n name.toLowerCase().endsWith('.journey.json')\n );\n const allJourneysData = { trees: {} };\n for (const file of jsonFiles) {\n const journeyData = JSON.parse(fs.readFileSync(file, 'utf8'));\n allJourneysData.trees[journeyData.tree._id] = journeyData;\n }\n importAllJourneys(\n allJourneysData.trees as MultiJourneyExportTemplate,\n options\n );\n}\n\n/**\n * Describe a journey\n * @param {SingleJourneyExportTemplate} journeyData journey export object\n */\nexport function describeJourney(\n journeyData: SingleJourneyExportTemplate\n): void {\n const nodeTypeMap = {};\n\n for (const nodeData of Object.values(journeyData.nodes)) {\n if (nodeTypeMap[nodeData['_type']['_id']]) {\n nodeTypeMap[nodeData['_type']['_id']] += 1;\n } else {\n nodeTypeMap[nodeData['_type']['_id']] = 1;\n }\n }\n\n for (const nodeData of Object.values(journeyData.innerNodes)) {\n if (nodeTypeMap[nodeData['_type']['_id']]) {\n nodeTypeMap[nodeData['_type']['_id']] += 1;\n } else {\n nodeTypeMap[nodeData['_type']['_id']] = 1;\n }\n }\n\n printMessage(`\\n${journeyData.tree._id}`, 'data');\n const line = Array(journeyData.tree._id['length']).fill('=').join('');\n printMessage(line);\n if (journeyData.tree.description) {\n printMessage(`\\n${journeyData.tree.description}\\n`, 'data');\n }\n\n printMessage('\\nNodes:', 'data');\n if (Object.entries(nodeTypeMap).length) {\n for (const [name, count] of Object.entries(nodeTypeMap)) {\n printMessage(`- [${String(count)['brightCyan']}] ${name}`, 'data');\n }\n }\n\n if (journeyData.themes?.length) {\n printMessage('\\nThemes:', 'data');\n for (const themeData of journeyData.themes) {\n printMessage(\n `- [${themeData['_id']['brightCyan']}] ${themeData['name']}`,\n 'data'\n );\n }\n }\n\n if (Object.entries(journeyData.scripts).length) {\n printMessage('\\nScripts:', 'data');\n for (const scriptData of Object.values(journeyData.scripts)) {\n printMessage(\n `- [${scriptData['_id'].brightCyan}] ${scriptData['name']}`,\n 'data'\n );\n }\n }\n\n if (Object.entries(journeyData.emailTemplates).length) {\n printMessage('\\nEmail Templates:', 'data');\n for (const templateData of Object.values(journeyData.emailTemplates)) {\n printMessage(`- ${templateData['_id'].split('/')[1]}`, 'data');\n }\n }\n\n if (Object.entries(journeyData.socialIdentityProviders).length) {\n printMessage('\\nSocial Identity Providers:', 'data');\n for (const socialIdpData of Object.values(\n journeyData.socialIdentityProviders\n )) {\n printMessage(\n `- ${socialIdpData['_id']} [${socialIdpData['_type']['_id'].brightCyan}]`,\n 'data'\n );\n }\n }\n\n if (Object.entries(journeyData.saml2Entities).length) {\n printMessage('\\nSAML2 Entity Providers:', 'data');\n for (const entityProviderData of Object.values(journeyData.saml2Entities)) {\n printMessage(`- ${entityProviderData['entityId']}`, 'data');\n }\n }\n\n if (Object.entries(journeyData.circlesOfTrust).length) {\n printMessage('\\nSAML2 Circles Of Trust:', 'data');\n for (const cotData of Object.values(journeyData.circlesOfTrust)) {\n printMessage(`- ${cotData['_id']}`, 'data');\n }\n }\n}\n\n/**\n * Find all node configuration objects that are no longer referenced by any tree\n * @returns {Promise<unknown[]>} a promise that resolves to an array of orphaned nodes\n */\nexport async function findOrphanedNodes(): Promise<unknown[]> {\n const allNodes = [];\n const orphanedNodes = [];\n let types = [];\n const allJourneys = (await getTrees()).result;\n let errorMessage = '';\n const errorTypes = [];\n\n createProgressIndicator(\n undefined,\n `Counting total nodes...`,\n 'indeterminate'\n );\n try {\n types = (await getNodeTypes()).result;\n } catch (error) {\n printMessage('Error retrieving all available node types:', 'error');\n printMessage(error.response.data, 'error');\n return [];\n }\n for (const type of types) {\n try {\n // eslint-disable-next-line no-await-in-loop, no-loop-func\n const nodes = (await getNodesByType(type._id)).result;\n for (const node of nodes) {\n allNodes.push(node);\n updateProgressIndicator(\n `${allNodes.length} total nodes${errorMessage}`\n );\n }\n } catch (error) {\n errorTypes.push(type._id);\n errorMessage = ` (Skipped type(s): ${errorTypes})`['yellow'];\n updateProgressIndicator(`${allNodes.length} total nodes${errorMessage}`);\n }\n }\n if (errorTypes.length > 0) {\n stopProgressIndicator(\n `${allNodes.length} total nodes${errorMessage}`,\n 'warn'\n );\n } else {\n stopProgressIndicator(`${allNodes.length} total nodes`, 'success');\n }\n\n createProgressIndicator(\n undefined,\n 'Counting active nodes...',\n 'indeterminate'\n );\n const activeNodes = [];\n for (const journey of allJourneys) {\n for (const nodeId in journey.nodes) {\n if ({}.hasOwnProperty.call(journey.nodes, nodeId)) {\n activeNodes.push(nodeId);\n updateProgressIndicator(`${activeNodes.length} active nodes`);\n const node = journey.nodes[nodeId];\n if (containerNodes.includes(node.nodeType)) {\n const containerNode = await getNode(nodeId, node.nodeType);\n for (const innerNode of containerNode.nodes) {\n activeNodes.push(innerNode._id);\n updateProgressIndicator(`${activeNodes.length} active nodes`);\n }\n }\n }\n }\n }\n stopProgressIndicator(`${activeNodes.length} active nodes`, 'success');\n\n createProgressIndicator(\n undefined,\n 'Calculating orphaned nodes...',\n 'indeterminate'\n );\n const diff = allNodes.filter((x) => !activeNodes.includes(x._id));\n for (const orphanedNode of diff) {\n orphanedNodes.push(orphanedNode);\n }\n stopProgressIndicator(`${orphanedNodes.length} orphaned nodes`, 'success');\n return orphanedNodes;\n}\n\n/**\n * Remove orphaned nodes\n * @param {[Object]} orphanedNodes Pass in an array of orphaned node configuration objects to remove\n * @returns {Promise<unknown[]>} a promise that resolves to an array nodes that encountered errors deleting\n */\nexport async function removeOrphanedNodes(\n orphanedNodes: unknown[]\n): Promise<unknown[]> {\n const errorNodes = [];\n createProgressIndicator(orphanedNodes.length, 'Removing orphaned nodes...');\n for (const node of orphanedNodes) {\n updateProgressIndicator(`Removing ${node['_id']}...`);\n try {\n // eslint-disable-next-line no-await-in-loop\n await deleteNode(node['_id'], node['_type']['_id']);\n } catch (deleteError) {\n errorNodes.push(node);\n printMessage(` ${deleteError}`, 'error');\n }\n }\n stopProgressIndicator(`Removed ${orphanedNodes.length} orphaned nodes.`);\n return errorNodes;\n}\n\n/**\n * Analyze if a journey contains any custom nodes considering the detected or the overridden version.\n * @param {SingleJourneyExportTemplate} journey Journey/tree configuration object\n * @returns {boolean} True if the journey/tree contains any custom nodes, false otherwise.\n */\nexport function isCustomJourney(journey: SingleJourneyExportTemplate) {\n const nodeList = Object.values(journey.nodes).concat(\n Object.values(journey.innerNodes)\n );\n for (const node of nodeList) {\n if (isCustomNode(node['_type']['_id'])) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Analyze if a journey contains any premium nodes considering the detected or the overridden version.\n * @param {SingleJourneyExportTemplate} journey Journey/tree configuration object\n * @returns {boolean} True if the journey/tree contains any custom nodes, false otherwise.\n */\nexport function isPremiumJourney(journey: SingleJourneyExportTemplate) {\n const nodeList = Object.values(journey.nodes).concat(\n Object.values(journey.innerNodes)\n );\n for (const node of nodeList) {\n if (isPremiumNode(node['_type']['_id'])) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Analyze if a journey contains any cloud-only nodes considering the detected or the overridden version.\n * @param {SingleJourneyExportTemplate} journey Journey/tree configuration object\n * @returns {boolean} True if the journey/tree contains any cloud-only nodes, false otherwise.\n */\nexport function isCloudOnlyJourney(journey: SingleJourneyExportTemplate) {\n const nodeList = Object.values(journey.nodes).concat(\n Object.values(journey.innerNodes)\n );\n for (const node of nodeList) {\n if (isCloudOnlyNode(node['_type']['_id'])) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Get a journey's classifications, which can be one or multiple of:\n * - standard: can run on any instance of a ForgeRock platform\n * - cloud: utilize nodes, which are exclusively available in the ForgeRock Identity Cloud\n * - premium: utilizes nodes, which come at a premium\n * @param {SingleJourneyExportTemplate} journey journey export data\n * @returns {string[]} an array of one or multiple classifications\n */\nexport function getJourneyClassification(\n journey: SingleJourneyExportTemplate\n): string[] {\n const classifications = [];\n const premium = isPremiumJourney(journey);\n const custom = isCustomJourney(journey);\n const cloud = isCloudOnlyJourney(journey);\n if (custom) {\n classifications.push('custom');\n } else if (cloud) {\n classifications.push('cloud');\n } else {\n classifications.push('standard');\n }\n if (premium) classifications.push('premium');\n return classifications;\n}\n\n/**\n * List all the journeys/trees\n * @param {boolean} long Long version, all the fields\n * @param {boolean} analyze Analyze journeys/trees for custom nodes (expensive)\n * @returns {Promise<unknown[]>} a promise that resolves to an array journey objects\n */\nexport async function listJourneys(\n long = false,\n analyze = false\n): Promise<unknown[]> {\n let journeys = [];\n journeys = await getJourneys();\n if (!long && !analyze) {\n for (const journeyStub of journeys) {\n printMessage(`${journeyStub['_id']}`, 'data');\n }\n } else {\n if (!analyze) {\n const table = createTable(['Name', 'Status', 'Tags']);\n for (const journeyStub of journeys) {\n table.push([\n `${journeyStub._id}`,\n journeyStub.enabled === false\n ? 'disabled'['brightRed']\n : 'enabled'['brightGreen'],\n journeyStub.uiConfig?.categories\n ? wordwrap(\n JSON.parse(journeyStub.uiConfig.categories).join(', '),\n 60\n )\n : '',\n ]);\n }\n printMessage(table.toString(), 'data');\n } else {\n createProgressIndicator(\n 0,\n 'Retrieving details of all journeys...',\n 'indeterminate'\n );\n const exportPromises = [];\n for (const journeyStub of journeys) {\n exportPromises.push(\n exportJourney(journeyStub['_id'], {\n useStringArrays: false,\n deps: false,\n verbose: false,\n })\n );\n }\n const journeyExports = await Promise.all(exportPromises);\n stopProgressIndicator('Retrieved details of all journeys.', 'success');\n const table = createTable(['Name', 'Status', 'Classification', 'Tags']);\n for (const journeyExport of journeyExports) {\n table.push([\n `${journeyExport.tree._id}`,\n journeyExport.tree.enabled === false\n ? 'disabled'['brightRed']\n : 'enabled'['brightGreen'],\n getJourneyClassification(journeyExport).join(', '),\n journeyExport.tree.uiConfig?.categories\n ? wordwrap(\n JSON.parse(journeyExport.tree.uiConfig.categories).join(', '),\n 60\n )\n : '',\n ]);\n }\n printMessage(table.toString(), 'data');\n }\n }\n return journeys;\n}\n\n/**\n * Delete a journey\n * @param {string} journeyId journey id/name\n * @param {Object} options deep=true also delete all the nodes and inner nodes, verbose=true print verbose info\n */\nexport async function deleteJourney(\n journeyId: string,\n options,\n progress = true\n) {\n const { deep } = options;\n const { verbose } = options;\n const status = { nodes: {} };\n if (progress)\n createProgressIndicator(\n undefined,\n `Deleting ${journeyId}...`,\n 'indeterminate'\n );\n if (progress && verbose) stopProgressIndicator();\n return deleteTree(journeyId)\n .then(async (deleteTreeResponse) => {\n status['status'] = 'success';\n const nodePromises = [];\n if (verbose) printMessage(`Deleted ${journeyId} (tree)`, 'info');\n if (deep) {\n for (const [nodeId, nodeObject] of Object.entries(\n deleteTreeResponse.nodes\n )) {\n // delete inner nodes (nodes inside container nodes)\n if (containerNodes.includes(nodeObject['nodeType'])) {\n try {\n // eslint-disable-next-line no-await-in-loop\n const containerNode = await getNode(\n nodeId,\n nodeObject['nodeType']\n );\n if (verbose)\n printMessage(\n `Read ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}`,\n 'info'\n );\n for (const innerNodeObject of containerNode.nodes) {\n nodePromises.push(\n deleteNode(innerNodeObject._id, innerNodeObject.nodeType)\n .then((response2) => {\n status.nodes[innerNodeObject._id] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${innerNodeObject._id} (${innerNodeObject.nodeType}) from ${journeyId}`,\n 'info'\n );\n return response2;\n })\n .catch((error) => {\n status.nodes[innerNodeObject._id] = {\n status: 'error',\n error,\n };\n if (verbose)\n printMessage(\n `Error deleting inner node ${innerNodeObject._id} (${innerNodeObject.nodeType}) from ${journeyId}: ${error}`,\n 'error'\n );\n })\n );\n }\n // finally delete the container node\n nodePromises.push(\n deleteNode(containerNode._id, containerNode['_type']['_id'])\n .then((response2) => {\n status.nodes[containerNode._id] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${containerNode._id} (${containerNode['_type']['_id']}) from ${journeyId}`,\n 'info'\n );\n return response2;\n })\n .catch((error) => {\n if (\n error?.response?.data?.code === 500 &&\n error.response.data.message ===\n 'Unable to read SMS config: Node did not exist'\n ) {\n status.nodes[containerNode._id] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${containerNode._id} (${containerNode['_type']['_id']}) from ${journeyId}`,\n 'info'\n );\n } else {\n status.nodes[containerNode._id] = {\n status: 'error',\n error,\n };\n if (verbose)\n printMessage(\n `Error deleting container node ${containerNode._id} (${containerNode['_type']['_id']}) from ${journeyId}: ${error.response.data.message}`,\n 'error'\n );\n }\n })\n );\n } catch (error) {\n if (verbose)\n printMessage(\n `Error getting container node ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}: ${error}`,\n 'error'\n );\n }\n } else {\n // delete the node\n nodePromises.push(\n deleteNode(nodeId, nodeObject['nodeType'])\n .then((response) => {\n status.nodes[nodeId] = { status: 'success' };\n if (verbose)\n printMessage(\n `Deleted ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}`,\n 'info'\n );\n return response;\n })\n .catch((error) => {\n status.nodes[nodeId] = { status: 'error', error };\n if (verbose)\n printMessage(\n `Error deleting node ${nodeId} (${nodeObject['nodeType']}) from ${journeyId}: ${error}`,\n 'error'\n );\n })\n );\n }\n }\n }\n // wait until all the node calls are complete\n await Promise.allSettled(nodePromises);\n\n // report status\n if (progress) {\n let nodeCount = 0;\n let errorCount = 0;\n for (const node of Object.keys(status.nodes)) {\n nodeCount += 1;\n if (status.nodes[node].status === 'error') errorCount += 1;\n }\n if (errorCount === 0) {\n stopProgressIndicator(\n `Deleted ${journeyId} and ${\n nodeCount - errorCount\n }/${nodeCount} nodes.`,\n 'success'\n );\n } else {\n stopProgressIndicator(\n `Deleted ${journeyId} and ${\n nodeCount - errorCount\n }/${nodeCount} nodes.`,\n 'fail'\n );\n }\n }\n return status;\n })\n .catch((error) => {\n status['status'] = 'error';\n status['error'] = error;\n stopProgressIndicator(`Error deleting ${journeyId}.`, 'fail');\n if (verbose)\n printMessage(`Error deleting tree ${journeyId}: ${error}`, 'error');\n return status;\n });\n}\n\n/**\n * Delete all journeys\n * @param {Object} options deep=true also delete all the nodes and inner nodes, verbose=true print verbose info\n */\nexport async function deleteJourneys(options) {\n const { verbose } = options;\n const status = {};\n const trees = (await getTrees()).result;\n createProgressIndicator(trees.length, 'Deleting journeys...');\n for (const tree of trees) {\n if (verbose) printMessage('');\n // eslint-disable-next-line no-await-in-loop\n status[tree._id] = await deleteJourney(tree._id, options, false);\n updateProgressIndicator(`${tree._id}`);\n // introduce a 100ms wait to allow the progress bar to update before the next verbose message prints from the async function\n if (verbose)\n // eslint-disable-next-line no-await-in-loop\n await new Promise((r) => {\n setTimeout(r, 100);\n });\n }\n let journeyCount = 0;\n let journeyErrorCount = 0;\n let nodeCount = 0;\n let nodeErrorCount = 0;\n for (const journey of Object.keys(status)) {\n journeyCount += 1;\n if (status[journey].status === 'error') journeyErrorCount += 1;\n for (const node of Object.keys(status[journey].nodes)) {\n nodeCount += 1;\n if (status[journey].nodes[node].status === 'error') nodeErrorCount += 1;\n }\n }\n stopProgressIndicator(\n `Deleted ${journeyCount - journeyErrorCount}/${journeyCount} journeys and ${\n nodeCount - nodeErrorCount\n }/${nodeCount} nodes.`\n );\n return status;\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find all node configuration objects that are no longer referenced by any tree
|
|
3
|
+
* @returns {Promise<unknown[]>} a promise that resolves to an array of orphaned nodes
|
|
4
|
+
*/
|
|
5
|
+
export declare function findOrphanedNodes(): Promise<unknown[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Remove orphaned nodes
|
|
8
|
+
* @param {[Object]} orphanedNodes Pass in an array of orphaned node configuration objects to remove
|
|
9
|
+
* @returns {Promise<unknown[]>} a promise that resolves to an array nodes that encountered errors deleting
|
|
10
|
+
*/
|
|
11
|
+
export declare function removeOrphanedNodes(orphanedNodes: unknown[]): Promise<unknown[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Analyze if a node is a premium node.
|
|
14
|
+
* @param {string} nodeType Node type
|
|
15
|
+
* @returns {boolean} True if the node type is premium, false otherwise.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isPremiumNode(nodeType: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Analyze if a node is a cloud-only node.
|
|
20
|
+
* @param {string} nodeType Node type
|
|
21
|
+
* @returns {boolean} True if the node type is cloud-only, false otherwise.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isCloudOnlyNode(nodeType: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Analyze if a node is custom.
|
|
26
|
+
* @param {string} nodeType Node type
|
|
27
|
+
* @returns {boolean} True if the node type is custom, false otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isCustomNode(nodeType: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ops/NodeOps.ts"],"names":[],"mappings":"AAsIA;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAgF5D;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,OAAO,EAAE,GACvB,OAAO,CAAC,OAAO,EAAE,CAAC,CAepB;AAiOD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CA6CtD","file":"NodeOps.d.ts","sourcesContent":["import _ from 'lodash';\nimport storage from '../storage/SessionStorage';\nimport {\n getNode,\n deleteNode,\n getNodeTypes,\n getNodesByType,\n} from '../api/NodeApi';\nimport { getTrees } from '../api/TreeApi';\nimport {\n printMessage,\n createProgressIndicator,\n updateProgressIndicator,\n stopProgressIndicator,\n} from './utils/Console';\nimport {\n getProviderByLocationAndId,\n getProviderMetadata,\n} from '../api/Saml2Api';\nimport { encodeBase64Url } from '../api/utils/Base64';\n\nconst containerNodes = ['PageNode', 'CustomPageNode'];\n\nconst scriptedNodes = [\n 'ConfigProviderNode',\n 'ScriptedDecisionNode',\n 'ClientScriptNode',\n 'SocialProviderHandlerNode',\n 'CustomScriptNode',\n];\n\nconst emailTemplateNodes = ['EmailSuspendNode', 'EmailTemplateNode'];\n\nconst emptyScriptPlaceholder = '[Empty]';\n\n/**\n * Helper to get all SAML2 dependencies for a given node object\n * @param {Object} nodeObject node object\n * @param {[Object]} allProviders array of all saml2 providers objects\n * @param {[Object]} allCirclesOfTrust array of all circle of trust objects\n * @returns {Promise} a promise that resolves to an object containing a saml2 dependencies\n */\nasync function getSaml2NodeDependencies(\n nodeObject,\n allProviders,\n allCirclesOfTrust\n) {\n const samlProperties = ['metaAlias', 'idpEntityId'];\n const saml2EntityPromises = [];\n for (const samlProperty of samlProperties) {\n // In the following line nodeObject[samlProperty] will look like '/alpha/iSPAzure'.\n const entityId =\n samlProperty === 'metaAlias'\n ? _.last(nodeObject[samlProperty].split('/'))\n : nodeObject[samlProperty];\n const entity = _.find(allProviders, { entityId });\n if (entity) {\n try {\n const providerResponse = await getProviderByLocationAndId(\n entity.location,\n entity._id\n );\n /**\n * Adding entityLocation here to the entityResponse because the import tool\n * needs to know whether the saml2 entity is remote or not (this will be removed\n * from the config before importing see updateSaml2Entity and createSaml2Entity functions).\n * Importing a remote saml2 entity is a slightly different request (see createSaml2Entity).\n */\n providerResponse.entityLocation = entity.location;\n\n if (entity.location === 'remote') {\n // get the xml representation of this entity and add it to the entityResponse;\n const metaDataResponse = await getProviderMetadata(\n providerResponse.entityId\n );\n providerResponse.base64EntityXML = encodeBase64Url(metaDataResponse);\n }\n saml2EntityPromises.push(providerResponse);\n } catch (error) {\n printMessage(error.message, 'error');\n }\n }\n }\n try {\n const saml2EntitiesPromisesResults = await Promise.all(saml2EntityPromises);\n const saml2Entities = [];\n for (const saml2Entity of saml2EntitiesPromisesResults) {\n if (saml2Entity) {\n saml2Entities.push(saml2Entity);\n }\n }\n const samlEntityIds = _.map(\n saml2Entities,\n (saml2EntityConfig) => `${saml2EntityConfig.entityId}|saml2`\n );\n const circlesOfTrust = _.filter(allCirclesOfTrust, (circleOfTrust) => {\n let hasEntityId = false;\n for (const trustedProvider of circleOfTrust.trustedProviders) {\n if (!hasEntityId && samlEntityIds.includes(trustedProvider)) {\n hasEntityId = true;\n }\n }\n return hasEntityId;\n });\n const saml2NodeDependencies = {\n saml2Entities,\n circlesOfTrust,\n };\n return saml2NodeDependencies;\n } catch (error) {\n printMessage(error.message, 'error');\n const saml2NodeDependencies = {\n saml2Entities: [],\n circlesOfTrust: [],\n };\n return saml2NodeDependencies;\n }\n}\n\n// export async function getTreeNodes(treeObject) {\n// const nodeList = Object.entries(treeObject.nodes);\n// const results = await Promise.allSettled(\n// nodeList.map(\n// async ([nodeId, nodeInfo]) => await getNode(nodeId, nodeInfo['nodeType'])\n// )\n// );\n// const nodes = results.filter((r) => r.status === 'fulfilled');\n// nodes.map((f) => {\n// return f.status;\n// });\n// const failedList = results.filter((r) => r.status === 'rejected');\n// return nodes;\n// }\n\n/**\n * Find all node configuration objects that are no longer referenced by any tree\n * @returns {Promise<unknown[]>} a promise that resolves to an array of orphaned nodes\n */\nexport async function findOrphanedNodes(): Promise<unknown[]> {\n const allNodes = [];\n const orphanedNodes = [];\n let types = [];\n const allJourneys = (await getTrees()).result;\n let errorMessage = '';\n const errorTypes = [];\n\n createProgressIndicator(\n undefined,\n `Counting total nodes...`,\n 'indeterminate'\n );\n try {\n types = (await getNodeTypes()).result;\n } catch (error) {\n printMessage('Error retrieving all available node types:', 'error');\n printMessage(error.response.data, 'error');\n return [];\n }\n for (const type of types) {\n try {\n // eslint-disable-next-line no-await-in-loop, no-loop-func\n const nodes = (await getNodesByType(type._id)).result;\n for (const node of nodes) {\n allNodes.push(node);\n updateProgressIndicator(\n `${allNodes.length} total nodes${errorMessage}`\n );\n }\n } catch (error) {\n errorTypes.push(type._id);\n errorMessage = ` (Skipped type(s): ${errorTypes})`['yellow'];\n updateProgressIndicator(`${allNodes.length} total nodes${errorMessage}`);\n }\n }\n if (errorTypes.length > 0) {\n stopProgressIndicator(\n `${allNodes.length} total nodes${errorMessage}`,\n 'warn'\n );\n } else {\n stopProgressIndicator(`${allNodes.length} total nodes`, 'success');\n }\n\n createProgressIndicator(\n undefined,\n 'Counting active nodes...',\n 'indeterminate'\n );\n const activeNodes = [];\n for (const journey of allJourneys) {\n for (const nodeId in journey.nodes) {\n if ({}.hasOwnProperty.call(journey.nodes, nodeId)) {\n activeNodes.push(nodeId);\n updateProgressIndicator(`${activeNodes.length} active nodes`);\n const node = journey.nodes[nodeId];\n if (containerNodes.includes(node.nodeType)) {\n const containerNode = await getNode(nodeId, node.nodeType);\n for (const innerNode of containerNode.nodes) {\n activeNodes.push(innerNode._id);\n updateProgressIndicator(`${activeNodes.length} active nodes`);\n }\n }\n }\n }\n }\n stopProgressIndicator(`${activeNodes.length} active nodes`, 'success');\n\n createProgressIndicator(\n undefined,\n 'Calculating orphaned nodes...',\n 'indeterminate'\n );\n const diff = allNodes.filter((x) => !activeNodes.includes(x._id));\n for (const orphanedNode of diff) {\n orphanedNodes.push(orphanedNode);\n }\n stopProgressIndicator(`${orphanedNodes.length} orphaned nodes`, 'success');\n return orphanedNodes;\n}\n\n/**\n * Remove orphaned nodes\n * @param {[Object]} orphanedNodes Pass in an array of orphaned node configuration objects to remove\n * @returns {Promise<unknown[]>} a promise that resolves to an array nodes that encountered errors deleting\n */\nexport async function removeOrphanedNodes(\n orphanedNodes: unknown[]\n): Promise<unknown[]> {\n const errorNodes = [];\n createProgressIndicator(orphanedNodes.length, 'Removing orphaned nodes...');\n for (const node of orphanedNodes) {\n updateProgressIndicator(`Removing ${node['_id']}...`);\n try {\n // eslint-disable-next-line no-await-in-loop\n await deleteNode(node['_id'], node['_type']['_id']);\n } catch (deleteError) {\n errorNodes.push(node);\n printMessage(` ${deleteError}`, 'error');\n }\n }\n stopProgressIndicator(`Removed ${orphanedNodes.length} orphaned nodes.`);\n return errorNodes;\n}\n\nconst OOTB_NODE_TYPES_7 = [\n 'AcceptTermsAndConditionsNode',\n 'AccountActiveDecisionNode',\n 'AccountLockoutNode',\n 'AgentDataStoreDecisionNode',\n 'AnonymousSessionUpgradeNode',\n 'AnonymousUserNode',\n 'AttributeCollectorNode',\n 'AttributePresentDecisionNode',\n 'AttributeValueDecisionNode',\n 'AuthLevelDecisionNode',\n 'ChoiceCollectorNode',\n 'ConsentNode',\n 'CookiePresenceDecisionNode',\n 'CreateObjectNode',\n 'CreatePasswordNode',\n 'DataStoreDecisionNode',\n 'DeviceGeoFencingNode',\n 'DeviceLocationMatchNode',\n 'DeviceMatchNode',\n 'DeviceProfileCollectorNode',\n 'DeviceSaveNode',\n 'DeviceTamperingVerificationNode',\n 'DisplayUserNameNode',\n 'EmailSuspendNode',\n 'EmailTemplateNode',\n 'IdentifyExistingUserNode',\n 'IncrementLoginCountNode',\n 'InnerTreeEvaluatorNode',\n 'IotAuthenticationNode',\n 'IotRegistrationNode',\n 'KbaCreateNode',\n 'KbaDecisionNode',\n 'KbaVerifyNode',\n 'LdapDecisionNode',\n 'LoginCountDecisionNode',\n 'MessageNode',\n 'MetadataNode',\n 'MeterNode',\n 'ModifyAuthLevelNode',\n 'OneTimePasswordCollectorDecisionNode',\n 'OneTimePasswordGeneratorNode',\n 'OneTimePasswordSmsSenderNode',\n 'OneTimePasswordSmtpSenderNode',\n 'PageNode',\n 'PasswordCollectorNode',\n 'PatchObjectNode',\n 'PersistentCookieDecisionNode',\n 'PollingWaitNode',\n 'ProfileCompletenessDecisionNode',\n 'ProvisionDynamicAccountNode',\n 'ProvisionIdmAccountNode',\n 'PushAuthenticationSenderNode',\n 'PushResultVerifierNode',\n 'QueryFilterDecisionNode',\n 'RecoveryCodeCollectorDecisionNode',\n 'RecoveryCodeDisplayNode',\n 'RegisterLogoutWebhookNode',\n 'RemoveSessionPropertiesNode',\n 'RequiredAttributesDecisionNode',\n 'RetryLimitDecisionNode',\n 'ScriptedDecisionNode',\n 'SelectIdPNode',\n 'SessionDataNode',\n 'SetFailureUrlNode',\n 'SetPersistentCookieNode',\n 'SetSessionPropertiesNode',\n 'SetSuccessUrlNode',\n 'SocialFacebookNode',\n 'SocialGoogleNode',\n 'SocialNode',\n 'SocialOAuthIgnoreProfileNode',\n 'SocialOpenIdConnectNode',\n 'SocialProviderHandlerNode',\n 'TermsAndConditionsDecisionNode',\n 'TimeSinceDecisionNode',\n 'TimerStartNode',\n 'TimerStopNode',\n 'UsernameCollectorNode',\n 'ValidatedPasswordNode',\n 'ValidatedUsernameNode',\n 'WebAuthnAuthenticationNode',\n 'WebAuthnDeviceStorageNode',\n 'WebAuthnRegistrationNode',\n 'ZeroPageLoginNode',\n 'product-CertificateCollectorNode',\n 'product-CertificateUserExtractorNode',\n 'product-CertificateValidationNode',\n 'product-KerberosNode',\n 'product-ReCaptchaNode',\n 'product-Saml2Node',\n 'product-WriteFederationInformationNode',\n];\n\nconst OOTB_NODE_TYPES_7_1 = [\n 'PushRegistrationNode',\n 'GetAuthenticatorAppNode',\n 'MultiFactorRegistrationOptionsNode',\n 'OptOutMultiFactorAuthenticationNode',\n].concat(OOTB_NODE_TYPES_7);\n\nconst OOTB_NODE_TYPES_7_2 = [\n 'OathRegistrationNode',\n 'OathTokenVerifierNode',\n 'PassthroughAuthenticationNode',\n 'ConfigProviderNode',\n 'DebugNode',\n].concat(OOTB_NODE_TYPES_7_1);\n\nconst OOTB_NODE_TYPES_7_3 = [].concat(OOTB_NODE_TYPES_7_2);\n\nconst OOTB_NODE_TYPES_6_5 = [\n 'AbstractSocialAuthLoginNode',\n 'AccountLockoutNode',\n 'AgentDataStoreDecisionNode',\n 'AnonymousUserNode',\n 'AuthLevelDecisionNode',\n 'ChoiceCollectorNode',\n 'CookiePresenceDecisionNode',\n 'CreatePasswordNode',\n 'DataStoreDecisionNode',\n 'InnerTreeEvaluatorNode',\n 'LdapDecisionNode',\n 'MessageNode',\n 'MetadataNode',\n 'MeterNode',\n 'ModifyAuthLevelNode',\n 'OneTimePasswordCollectorDecisionNode',\n 'OneTimePasswordGeneratorNode',\n 'OneTimePasswordSmsSenderNode',\n 'OneTimePasswordSmtpSenderNode',\n 'PageNode',\n 'PasswordCollectorNode',\n 'PersistentCookieDecisionNode',\n 'PollingWaitNode',\n 'ProvisionDynamicAccountNode',\n 'ProvisionIdmAccountNode',\n 'PushAuthenticationSenderNode',\n 'PushResultVerifierNode',\n 'RecoveryCodeCollectorDecisionNode',\n 'RecoveryCodeDisplayNode',\n 'RegisterLogoutWebhookNode',\n 'RemoveSessionPropertiesNode',\n 'RetryLimitDecisionNode',\n 'ScriptedDecisionNode',\n 'SessionDataNode',\n 'SetFailureUrlNode',\n 'SetPersistentCookieNode',\n 'SetSessionPropertiesNode',\n 'SetSuccessUrlNode',\n 'SocialFacebookNode',\n 'SocialGoogleNode',\n 'SocialNode',\n 'SocialOAuthIgnoreProfileNode',\n 'SocialOpenIdConnectNode',\n 'TimerStartNode',\n 'TimerStopNode',\n 'UsernameCollectorNode',\n 'WebAuthnAuthenticationNode',\n 'WebAuthnRegistrationNode',\n 'ZeroPageLoginNode',\n];\n\nconst OOTB_NODE_TYPES_6 = [\n 'AbstractSocialAuthLoginNode',\n 'AccountLockoutNode',\n 'AgentDataStoreDecisionNode',\n 'AnonymousUserNode',\n 'AuthLevelDecisionNode',\n 'ChoiceCollectorNode',\n 'CookiePresenceDecisionNode',\n 'CreatePasswordNode',\n 'DataStoreDecisionNode',\n 'InnerTreeEvaluatorNode',\n 'LdapDecisionNode',\n 'MessageNode',\n 'MetadataNode',\n 'MeterNode',\n 'ModifyAuthLevelNode',\n 'OneTimePasswordCollectorDecisionNode',\n 'OneTimePasswordGeneratorNode',\n 'OneTimePasswordSmsSenderNode',\n 'OneTimePasswordSmtpSenderNode',\n 'PageNode',\n 'PasswordCollectorNode',\n 'PersistentCookieDecisionNode',\n 'PollingWaitNode',\n 'ProvisionDynamicAccountNode',\n 'ProvisionIdmAccountNode',\n 'PushAuthenticationSenderNode',\n 'PushResultVerifierNode',\n 'RecoveryCodeCollectorDecisionNode',\n 'RecoveryCodeDisplayNode',\n 'RegisterLogoutWebhookNode',\n 'RemoveSessionPropertiesNode',\n 'RetryLimitDecisionNode',\n 'ScriptedDecisionNode',\n 'SessionDataNode',\n 'SetFailureUrlNode',\n 'SetPersistentCookieNode',\n 'SetSessionPropertiesNode',\n 'SetSuccessUrlNode',\n 'SocialFacebookNode',\n 'SocialGoogleNode',\n 'SocialNode',\n 'SocialOAuthIgnoreProfileNode',\n 'SocialOpenIdConnectNode',\n 'TimerStartNode',\n 'TimerStopNode',\n 'UsernameCollectorNode',\n 'WebAuthnAuthenticationNode',\n 'WebAuthnRegistrationNode',\n 'ZeroPageLoginNode',\n];\n\nconst CLOUD_ONLY_NODE_TYPES = ['IdentityStoreDecisionNode'];\n\nconst PREMIUM_NODE_TYPES = [\n 'AutonomousAccessSignalNode',\n 'AutonomousAccessDecisionNode',\n 'AutonomousAccessResultNode',\n];\n\n/**\n * Analyze if a node is a premium node.\n * @param {string} nodeType Node type\n * @returns {boolean} True if the node type is premium, false otherwise.\n */\nexport function isPremiumNode(nodeType: string): boolean {\n return PREMIUM_NODE_TYPES.includes(nodeType);\n}\n\n/**\n * Analyze if a node is a cloud-only node.\n * @param {string} nodeType Node type\n * @returns {boolean} True if the node type is cloud-only, false otherwise.\n */\nexport function isCloudOnlyNode(nodeType: string): boolean {\n return CLOUD_ONLY_NODE_TYPES.includes(nodeType);\n}\n\n/**\n * Analyze if a node is custom.\n * @param {string} nodeType Node type\n * @returns {boolean} True if the node type is custom, false otherwise.\n */\nexport function isCustomNode(nodeType: string): boolean {\n let ootbNodeTypes = [];\n switch (storage.session.getAmVersion()) {\n case '7.1.0':\n ootbNodeTypes = OOTB_NODE_TYPES_7_1.slice(0);\n break;\n case '7.2.0':\n ootbNodeTypes = OOTB_NODE_TYPES_7_2.slice(0);\n break;\n case '7.3.0':\n ootbNodeTypes = OOTB_NODE_TYPES_7_3.slice(0);\n break;\n case '7.0.0':\n case '7.0.1':\n case '7.0.2':\n ootbNodeTypes = OOTB_NODE_TYPES_7.slice(0);\n break;\n case '6.5.3':\n case '6.5.2.3':\n case '6.5.2.2':\n case '6.5.2.1':\n case '6.5.2':\n case '6.5.1':\n case '6.5.0.2':\n case '6.5.0.1':\n ootbNodeTypes = OOTB_NODE_TYPES_6_5.slice(0);\n break;\n case '6.0.0.7':\n case '6.0.0.6':\n case '6.0.0.5':\n case '6.0.0.4':\n case '6.0.0.3':\n case '6.0.0.2':\n case '6.0.0.1':\n case '6.0.0':\n ootbNodeTypes = OOTB_NODE_TYPES_6.slice(0);\n break;\n default:\n return true;\n }\n return (\n !ootbNodeTypes.includes(nodeType) &&\n !isPremiumNode(nodeType) &&\n !isCloudOnlyNode(nodeType)\n );\n}\n"]}
|
|
@@ -3,7 +3,9 @@ declare const _default: {
|
|
|
3
3
|
setItem: (key: any, value: any) => any;
|
|
4
4
|
getItem: (key: any) => any;
|
|
5
5
|
removeItem: (key: any) => boolean;
|
|
6
|
-
raw: {
|
|
6
|
+
raw: {
|
|
7
|
+
authenticationHeaderOverrides: {};
|
|
8
|
+
};
|
|
7
9
|
setUsername: (value: any) => any;
|
|
8
10
|
getUsername: () => any;
|
|
9
11
|
setPassword: (value: any) => any;
|
|
@@ -18,6 +20,10 @@ declare const _default: {
|
|
|
18
20
|
getCookieName: () => any;
|
|
19
21
|
setCookieValue: (value: any) => any;
|
|
20
22
|
getCookieValue: () => any;
|
|
23
|
+
setAuthenticationService: (value: any) => any;
|
|
24
|
+
getAuthenticationService: () => any;
|
|
25
|
+
setAuthenticationHeaderOverrides: (value: any) => any;
|
|
26
|
+
getAuthenticationHeaderOverrides: () => {};
|
|
21
27
|
setBearerToken: (value: any) => any;
|
|
22
28
|
getBearerToken: () => any;
|
|
23
29
|
setLogApiKey: (value: any) => any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage/SessionStorage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/storage/SessionStorage.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,wBA2DE","file":"SessionStorage.d.ts","sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst pkg = JSON.parse(\n fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8')\n);\n\nconst _sessionStorage = {\n authenticationHeaderOverrides: {},\n};\n\nexport default {\n session: {\n setItem: (key, value) => (_sessionStorage[key] = value),\n getItem: (key) => _sessionStorage[key],\n removeItem: (key) => delete _sessionStorage[key],\n raw: _sessionStorage,\n setUsername: (value) => (_sessionStorage['username'] = value),\n getUsername: () => _sessionStorage['username'],\n setPassword: (value) => (_sessionStorage['password'] = value),\n getPassword: () => _sessionStorage['password'],\n setTenant: (value) => (_sessionStorage['tenant'] = value),\n getTenant: () => _sessionStorage['tenant'],\n setDeploymentType: (value) => (_sessionStorage['deploymentType'] = value),\n getDeploymentType: () => _sessionStorage['deploymentType'],\n setRealm: (value) => (_sessionStorage['realm'] = value),\n getRealm: () => _sessionStorage['realm'],\n setCookieName: (value) => (_sessionStorage['cookieName'] = value),\n getCookieName: () => _sessionStorage['cookieName'],\n setCookieValue: (value) => (_sessionStorage['cookieValue'] = value),\n getCookieValue: () => _sessionStorage['cookieValue'],\n setAuthenticationService: (value) =>\n (_sessionStorage['authenticationService'] = value),\n getAuthenticationService: () => _sessionStorage['authenticationService'],\n setAuthenticationHeaderOverrides: (value) =>\n (_sessionStorage['authenticationHeaderOverrides'] = value),\n getAuthenticationHeaderOverrides: () =>\n _sessionStorage['authenticationHeaderOverrides'],\n setBearerToken: (value) => (_sessionStorage['bearerToken'] = value),\n getBearerToken: () => _sessionStorage['bearerToken'],\n setLogApiKey: (value) => (_sessionStorage['logApiKey'] = value),\n getLogApiKey: () => _sessionStorage['logApiKey'],\n setLogApiSecret: (value) => (_sessionStorage['logApiSecret'] = value),\n getLogApiSecret: () => _sessionStorage['logApiSecret'],\n setAmVersion: (value) => (_sessionStorage['amVersion'] = value),\n getAmVersion: () => _sessionStorage['amVersion'],\n setFrodoVersion: (value) => (_sessionStorage['frodoVersion'] = value),\n getFrodoVersion: () =>\n _sessionStorage['frodoVersion'] || `v${pkg.version} [${process.version}]`,\n setAllowInsecureConnection: (value) =>\n (_sessionStorage['insecure'] = value),\n getAllowInsecureConnection: () => _sessionStorage['insecure'],\n setConnectionProfilesPath: (value) =>\n (_sessionStorage['connectionProfilesPath'] = value),\n getConnectionProfilesPath: () => _sessionStorage['connectionProfilesPath'],\n setMasterKeyPath: (value) => (_sessionStorage['masterKeyPath'] = value),\n getMasterKeyPath: () => _sessionStorage['masterKeyPath'],\n setPrintHandler: (printHandler) =>\n (_sessionStorage['printHandler'] = printHandler),\n getPrintHandler: () => _sessionStorage['printHandler'],\n setCreateProgressHandler: (handler) =>\n (_sessionStorage['createProgressHandler'] = handler),\n getCreateProgressHandler: () => _sessionStorage['createProgressHandler'],\n setUpdateProgressHandler: (handler) =>\n (_sessionStorage['updateProgressHandler'] = handler),\n getUpdateProgressHandler: () => _sessionStorage['updateProgressHandler'],\n setStopProgressHandler: (handler) =>\n (_sessionStorage['stopProgressHandler'] = handler),\n getStopProgressHandler: () => _sessionStorage['stopProgressHandler'],\n },\n};\n"]}
|