import type { IWorkflowTemplateNode, IWorkflowTemplateNodeCredentials } from '@/Interface'; import type { NodeTypeProvider } from '@/utils/nodeTypes/nodeTypeTransforms'; import { getNodeTypeDisplayableCredentials } from '@/utils/nodes/nodeTransforms'; import type { NormalizedTemplateNodeCredentials } from '@/utils/templates/templateTypes'; import type { INodeCredentialDescription, INodeCredentials, INodeCredentialsDetails, } from 'n8n-workflow'; export type IWorkflowTemplateNodeWithCredentials = IWorkflowTemplateNode & Required>; const credentialKeySymbol = Symbol('credentialKey'); /** * A key that uniquely identifies a credential in a template node. * It encodes the credential type name and the credential name. * Uses a symbol typing trick to get nominal typing. * Use `keyFromCredentialTypeAndName` to create a key. */ export type TemplateCredentialKey = string & { [credentialKeySymbol]: never }; export type TemplateNodeWithRequiredCredential = { node: IWorkflowTemplateNode; requiredCredentials: INodeCredentialDescription[]; }; /** * Forms a key from credential type name and credential name */ export const keyFromCredentialTypeAndName = ( credentialTypeName: string, credentialName: string, ): TemplateCredentialKey => `${credentialTypeName}-${credentialName}` as TemplateCredentialKey; /** * Normalizes the credentials of a template node. Templates created with * different versions of n8n may have different credential formats. */ export const normalizeTemplateNodeCredentials = ( credentials?: IWorkflowTemplateNodeCredentials, ): NormalizedTemplateNodeCredentials => { if (!credentials) { return {}; } return Object.fromEntries( Object.entries(credentials).map(([key, value]) => { return typeof value === 'string' ? [key, value] : [key, value.name]; }), ); }; /** * Replaces the credentials of a node with the given replacements * * @example * const nodeCredentials = { twitterOAuth1Api: "twitter" }; * const toReplaceByKey = { 'twitterOAuth1Api-twitter': { * id: "BrEOZ5Cje6VYh9Pc", * name: "X OAuth account" * }}; * replaceTemplateNodeCredentials(nodeCredentials, toReplaceByKey); * // => { twitterOAuth1Api: { id: "BrEOZ5Cje6VYh9Pc", name: "X OAuth account" } } */ export const getReplacedTemplateNodeCredentials = ( nodeCredentials: IWorkflowTemplateNodeCredentials | undefined, toReplaceByKey: Record, ) => { if (!nodeCredentials) { return undefined; } const replacedNodeCredentials: INodeCredentials = {}; const normalizedCredentials = normalizeTemplateNodeCredentials(nodeCredentials); for (const credentialType in normalizedCredentials) { const credentialNameInTemplate = normalizedCredentials[credentialType]; const key = keyFromCredentialTypeAndName(credentialType, credentialNameInTemplate); const toReplaceWith = toReplaceByKey[key]; if (toReplaceWith) { replacedNodeCredentials[credentialType] = toReplaceWith; } } return replacedNodeCredentials; }; /** * Returns credentials for the given node that are missing from it * but are present in the given replacements */ export const getMissingTemplateNodeCredentials = ( nodeTypeProvider: NodeTypeProvider, node: IWorkflowTemplateNode, replacementsByKey: Record, ): INodeCredentials => { const nodeCredentialsToAdd: INodeCredentials = {}; const usableCredentials = getNodeTypeDisplayableCredentials(nodeTypeProvider, node); for (const usableCred of usableCredentials) { const credentialKey = keyFromCredentialTypeAndName(usableCred.name, ''); if (replacementsByKey[credentialKey]) { nodeCredentialsToAdd[usableCred.name] = replacementsByKey[credentialKey]; } } return nodeCredentialsToAdd; }; /** * Replaces the credentials of all template workflow nodes with the given * replacements */ export const replaceAllTemplateNodeCredentials = ( nodeTypeProvider: NodeTypeProvider, nodes: IWorkflowTemplateNode[], toReplaceWith: Record, ) => { return nodes.map((node) => { const replacedCredentials = getReplacedTemplateNodeCredentials(node.credentials, toReplaceWith); const newCredentials = getMissingTemplateNodeCredentials(nodeTypeProvider, node, toReplaceWith); const credentials = { ...replacedCredentials, ...newCredentials, }; return { ...node, credentials: Object.keys(credentials).length > 0 ? credentials : undefined, }; }); };