UNPKG

@o3r/third-party

Version:

This module provides a bridge to communicate with third parties via an iFrame solution.

162 lines • 8.43 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ngAddIframe = void 0; exports.ngAddIframeFn = ngAddIframeFn; const node_path_1 = require("node:path"); const schematics_1 = require("@angular-devkit/schematics"); const schematics_2 = require("@o3r/schematics"); const ts = require("typescript"); const iframeProperties = [ 'frame', 'bridge' ]; const checkIframePresence = (componentPath, tree) => { const sourceFile = ts.createSourceFile(componentPath, tree.readText(componentPath), ts.ScriptTarget.ES2020, true); const classStatement = sourceFile.statements.find((statement) => ts.isClassDeclaration(statement)); if (classStatement?.members.find((classElement) => ts.isPropertyDeclaration(classElement) && ts.isIdentifier(classElement.name) && iframeProperties.includes(classElement.name.escapedText.toString()))) { throw new schematics_2.O3rCliError(`Unable to add iframe to this component because it already has at least one of these properties: ${iframeProperties.join(', ')}.`); } }; /** * Add iframe to an existing component * @param options */ function ngAddIframeFn(options) { return async (tree, context) => { try { const { templateRelativePath } = (0, schematics_2.getO3rComponentInfoOrThrowIfNotFound)(tree, options.path); checkIframePresence(options.path, tree); const updateComponentFile = (0, schematics_1.chain)([ (0, schematics_2.addImportsRule)(options.path, [ { from: '@angular/core', importNames: [ 'AfterViewInit', 'DestroyRef', 'ElementRef', 'inject', 'viewChild' ] }, { from: '@angular/core/rxjs-interop', importNames: [ 'takeUntilDestroyed' ] }, { from: '@o3r/third-party', importNames: [ 'generateIFrameContent', 'IframeBridge' ] } ]), () => { const sourceFile = ts.createSourceFile(options.path, tree.readText(options.path), ts.ScriptTarget.ES2020, true); const result = ts.transform(sourceFile, [ (ctx) => (rootNode) => { const { factory } = ctx; const visit = (node) => { if (ts.isClassDeclaration(node)) { const implementsClauses = node.heritageClauses?.find((heritageClause) => heritageClause.token === ts.SyntaxKind.ImplementsKeyword); const interfaceToImplements = (0, schematics_2.generateImplementsExpressionWithTypeArguments)('AfterViewInit'); const deduplicateHeritageClauses = (clauses) => clauses.filter((h, i) => !clauses.slice(i + 1).some((h2) => h2.kind === h.kind && h2.expression.escapedText === h.expression.escapedText)); const newImplementsClauses = implementsClauses ? factory.updateHeritageClause(implementsClauses, deduplicateHeritageClauses([...implementsClauses.types, ...interfaceToImplements])) : factory.createHeritageClause(ts.SyntaxKind.ImplementsKeyword, [...interfaceToImplements]); const heritageClauses = Array.from(node.heritageClauses ?? []) .filter((h) => h.token !== ts.SyntaxKind.ImplementsKeyword) .concat(newImplementsClauses); const newModifiers = [] .concat(ts.getDecorators(node) || []) .concat(ts.getModifiers(node) || []); const propertiesToAdd = (0, schematics_2.generateClassElementsFromString)(` private frame = viewChild.required<ElementRef<HTMLIFrameElement>>('frame'); private bridge?: IframeBridge; private readonly destroyRef = inject(DestroyRef); `); const newNgAfterViewInit = (0, schematics_2.getSimpleUpdatedMethod)(node, factory, 'ngAfterViewInit', (0, schematics_2.generateBlockStatementsFromString)(` const nativeElem = this.frame().nativeElement; if (nativeElem.contentDocument) { nativeElem.contentDocument.write( generateIFrameContent( '', // third-party-script-url '' // third-party-html-headers-to-add ) ); nativeElem.contentDocument.close(); } if (nativeElem.contentWindow) { this.bridge = new IframeBridge(window, nativeElem); this.bridge.messages$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((message) => { switch (message.action) { // custom logic based on received message default: console.warn('Received unsupported action: ', message.action); } }); } `)); const newMembers = node.members .filter((classElement) => !(0, schematics_2.findMethodByName)('ngAfterViewInit')(classElement)) .concat(propertiesToAdd, newNgAfterViewInit) .sort(schematics_2.sortClassElement); (0, schematics_2.addCommentsOnClassProperties)(newMembers, { bridge: 'Iframe object template reference' }); return factory.updateClassDeclaration(node, newModifiers, node.name, node.typeParameters, heritageClauses, newMembers); } return ts.visitEachChild(node, visit, ctx); }; return ts.visitNode(rootNode, visit); }, schematics_2.fixStringLiterals ]); const printer = ts.createPrinter({ removeComments: false, newLine: ts.NewLineKind.LineFeed }); tree.overwrite(options.path, printer.printFile(result.transformed[0])); return tree; } ]); const updateTemplateFile = () => { const templatePath = templateRelativePath && node_path_1.posix.join((0, node_path_1.dirname)(options.path), templateRelativePath); if (templatePath && tree.exists(templatePath)) { tree.commitUpdate(tree .beginUpdate(templatePath) .insertLeft(0, '<iframe #frame src="about:blank" style="display: none"></iframe>\n')); } return tree; }; return (0, schematics_1.chain)([ updateComponentFile, updateTemplateFile, options.skipLinter ? (0, schematics_1.noop)() : (0, schematics_2.applyEsLintFix)() ]); } catch (e) { if (e instanceof schematics_2.NoOtterComponent && context.interactive) { const shouldConvertComponent = await (0, schematics_2.askConfirmationToConvertComponent)(); if (shouldConvertComponent) { return (0, schematics_1.chain)([ (0, schematics_1.externalSchematic)('@o3r/core', 'convert-component', { path: options.path }), ngAddIframeFn(options) ]); } } throw e; } }; } /** * Add iframe to an existing component * @param options */ exports.ngAddIframe = (0, schematics_2.createOtterSchematic)(ngAddIframeFn); //# sourceMappingURL=index.js.map