@o3r/third-party
Version:
This module provides a bridge to communicate with third parties via an iFrame solution.
162 lines • 8.43 kB
JavaScript
;
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