From b63b192886b347070e3ba7ee7b047e97d7ff8584 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 3 Jan 2025 14:37:03 -0500 Subject: [PATCH 1/7] add fix --- .../opf-resource-loader.service.spec.ts | 2 + .../services/opf-resource-loader.service.ts | 74 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts index 76b77c4b9d4..b4d0c8bcda0 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts @@ -50,11 +50,13 @@ describe('OpfResourceLoaderService', () => { const mockScriptResource = { url: 'script-url', type: OpfDynamicScriptResourceType.SCRIPT, + attributes: [{ key: 'opf-load-once', value: 'true' }], }; const mockStyleResource = { url: 'style-url', type: OpfDynamicScriptResourceType.STYLES, + attributes: [{ key: 'opf-load-once', value: 'true' }], }; spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index c9020a9d3e3..bc295f90bdb 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -22,11 +22,12 @@ export class OpfResourceLoaderService { protected document = inject(DOCUMENT); protected platformId = inject(PLATFORM_ID); - protected readonly OPF_RESOURCE_ATTRIBUTE_KEY = 'data-opf-resource'; protected readonly CORS_DEFAULT_VALUE = 'anonymous'; + protected readonly OPF_RESOURCE_LOAD_ONCE_ATTR_KEY = 'opf-load-once'; + protected readonly OPF_RESOURCE_ATTRIBUTE_KEY = 'data-opf-resource'; protected embedStyles(embedOptions: { - attributes?: OpfKeyValueMap[]; + attributes?: { [key: string]: string }; src: string; sri?: string; callback?: EventListener; @@ -38,30 +39,22 @@ export class OpfResourceLoaderService { link.href = src; link.rel = 'stylesheet'; link.type = 'text/css'; - link.setAttribute(this.OPF_RESOURCE_ATTRIBUTE_KEY, 'true'); if (sri) { link.integrity = sri; - const corsKeyvalue = attributes?.find( - (attr) => attr.key === 'crossorigin' && !!attr.value?.length - ); - link.crossOrigin = corsKeyvalue?.value ?? this.CORS_DEFAULT_VALUE; + link.crossOrigin = attributes?.['crossorigin'] ?? this.CORS_DEFAULT_VALUE; + + attributes?.['crossorigin'] && delete attributes?.['crossorigin']; } - if (attributes?.length) { - attributes.forEach((attribute) => { - const { key, value } = attribute; + + attributes && + Object.keys(attributes)?.forEach((key) => { if (!(key in link)) { - link.setAttribute(key, value); + link.setAttribute(key, attributes[key as keyof object]); } }); - } - if (callback) { - link.addEventListener('load', callback); - } - - if (errorCallback) { - link.addEventListener('error', errorCallback); - } + callback && link.addEventListener('load', callback); + errorCallback && link.addEventListener('error', errorCallback); this.document.head.appendChild(link); } @@ -74,6 +67,21 @@ export class OpfResourceLoaderService { return this.scriptLoader.hasScript(src); } + protected handleOpfAttribute(keyValueList?: OpfKeyValueMap[] | undefined): { + [key: string]: string; + } { + const attributes: { [key: string]: string } = {}; + keyValueList?.forEach((keyValue: OpfKeyValueMap) => { + attributes[keyValue.key] = keyValue.value; + }); + if (!attributes[this.OPF_RESOURCE_LOAD_ONCE_ATTR_KEY]) { + attributes[this.OPF_RESOURCE_ATTRIBUTE_KEY] = 'true'; + } else { + delete attributes[this.OPF_RESOURCE_LOAD_ONCE_ATTR_KEY]; + } + return attributes; + } + /** * Loads a script specified in the resource object. * @@ -82,31 +90,21 @@ export class OpfResourceLoaderService { */ protected loadScript(resource: OpfDynamicScriptResource): Promise { return new Promise((resolve, reject) => { - const attributes: any = { + const attributes: { [key: string]: string } = { type: 'text/javascript', - [this.OPF_RESOURCE_ATTRIBUTE_KEY]: true, + ...this.handleOpfAttribute(resource.attributes), }; if (resource?.sri) { attributes['integrity'] = resource.sri; - const corsKeyvalue: OpfKeyValueMap | undefined = - resource?.attributes?.find( - (attr) => attr.key === 'crossorigin' && !!attr.value?.length - ); - attributes['crossOrigin'] = - corsKeyvalue?.value ?? this.CORS_DEFAULT_VALUE; - } - - if (resource.attributes) { - resource.attributes.forEach((attribute) => { - attributes[attribute.key] = attribute.value; - }); + const corsKeyvalue: string | undefined = attributes?.['crossorigin']; + attributes['crossOrigin'] = corsKeyvalue ?? this.CORS_DEFAULT_VALUE; + corsKeyvalue && delete attributes?.['crossorigin']; } - - if (resource.url && !this.hasScript(resource.url)) { + if (resource?.url && !this.hasScript(resource.url)) { this.scriptLoader.embedScript({ - src: resource.url, - attributes: attributes, + src: resource.url as string, + attributes, callback: () => resolve(), errorCallback: () => reject(), disableKeyRestriction: true, @@ -127,7 +125,7 @@ export class OpfResourceLoaderService { return new Promise((resolve, reject) => { if (resource.url && !this.hasStyles(resource.url)) { this.embedStyles({ - attributes: resource?.attributes, + attributes: this.handleOpfAttribute(resource?.attributes), src: resource.url, sri: resource?.sri, callback: () => resolve(), From 55147dbf0c50337699cff3d0ed1c9f4bfe6c49ce Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 3 Jan 2025 14:54:07 -0500 Subject: [PATCH 2/7] fix sonar --- .../opf/base/root/services/opf-resource-loader.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index bc295f90bdb..f1fd2bfe4bb 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -103,8 +103,8 @@ export class OpfResourceLoaderService { } if (resource?.url && !this.hasScript(resource.url)) { this.scriptLoader.embedScript({ - src: resource.url as string, attributes, + src: resource.url, callback: () => resolve(), errorCallback: () => reject(), disableKeyRestriction: true, From 790e6a56aedaf8ac1aaf3fc2173e74635244bf18 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Tue, 7 Jan 2025 11:45:41 -0500 Subject: [PATCH 3/7] rename and add comment --- .../root/services/opf-resource-loader.service.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index f1fd2bfe4bb..8deb4b0fc3a 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -67,7 +67,16 @@ export class OpfResourceLoaderService { return this.scriptLoader.hasScript(src); } - protected handleOpfAttribute(keyValueList?: OpfKeyValueMap[] | undefined): { + /** + * Create attributes intended to script and link elements. + * + * Return attributes list including keyValueList and OPF specific attribute with below logic: + * + * 1. Resource loads only once: 'opf-load-once' key detected, no additional attribute added. + * 2. Resource deleted at page/payment change: 'data-opf-resource' attribute is added. + */ + + protected createAttributesList(keyValueList?: OpfKeyValueMap[] | undefined): { [key: string]: string; } { const attributes: { [key: string]: string } = {}; @@ -92,7 +101,7 @@ export class OpfResourceLoaderService { return new Promise((resolve, reject) => { const attributes: { [key: string]: string } = { type: 'text/javascript', - ...this.handleOpfAttribute(resource.attributes), + ...this.createAttributesList(resource.attributes), }; if (resource?.sri) { @@ -125,7 +134,7 @@ export class OpfResourceLoaderService { return new Promise((resolve, reject) => { if (resource.url && !this.hasStyles(resource.url)) { this.embedStyles({ - attributes: this.handleOpfAttribute(resource?.attributes), + attributes: this.createAttributesList(resource?.attributes), src: resource.url, sri: resource?.sri, callback: () => resolve(), From 317831b8a70e4602e3d600d3431366498d5eeca5 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Tue, 7 Jan 2025 12:56:55 -0500 Subject: [PATCH 4/7] reformat conditions --- .../base/root/services/opf-resource-loader.service.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index 8deb4b0fc3a..f2483df6e2d 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -53,8 +53,13 @@ export class OpfResourceLoaderService { } }); - callback && link.addEventListener('load', callback); - errorCallback && link.addEventListener('error', errorCallback); + if (callback) { + link.addEventListener('load', callback); + } + + if (errorCallback) { + link.addEventListener('error', errorCallback); + } this.document.head.appendChild(link); } From 5c21bf1b11272acb015f4d4d727f9023b83fa1e4 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Tue, 7 Jan 2025 13:56:07 -0500 Subject: [PATCH 5/7] remove condition --- .../opf/base/root/services/opf-resource-loader.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index f2483df6e2d..d31150d236c 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -42,8 +42,7 @@ export class OpfResourceLoaderService { if (sri) { link.integrity = sri; link.crossOrigin = attributes?.['crossorigin'] ?? this.CORS_DEFAULT_VALUE; - - attributes?.['crossorigin'] && delete attributes?.['crossorigin']; + delete attributes?.['crossorigin']; } attributes && From 4d7560e51f7806fae3893d431bcf36e56f4c8d1e Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Wed, 8 Jan 2025 09:23:24 -0500 Subject: [PATCH 6/7] improve condition --- .../opf/base/root/services/opf-resource-loader.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index d31150d236c..2e5dd7444b2 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -110,9 +110,9 @@ export class OpfResourceLoaderService { if (resource?.sri) { attributes['integrity'] = resource.sri; - const corsKeyvalue: string | undefined = attributes?.['crossorigin']; - attributes['crossOrigin'] = corsKeyvalue ?? this.CORS_DEFAULT_VALUE; - corsKeyvalue && delete attributes?.['crossorigin']; + attributes['crossOrigin'] = + attributes?.['crossorigin'] ?? this.CORS_DEFAULT_VALUE; + delete attributes?.['crossorigin']; } if (resource?.url && !this.hasScript(resource.url)) { this.scriptLoader.embedScript({ From 89c76185a55b233ae032e86d64cc0e3f6b8b9ae4 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Thu, 9 Jan 2025 12:11:25 -0500 Subject: [PATCH 7/7] rework --- .../base/root/services/opf-resource-loader.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index 2e5dd7444b2..5bb3fb4e9d7 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -23,7 +23,7 @@ export class OpfResourceLoaderService { protected platformId = inject(PLATFORM_ID); protected readonly CORS_DEFAULT_VALUE = 'anonymous'; - protected readonly OPF_RESOURCE_LOAD_ONCE_ATTR_KEY = 'opf-load-once'; + protected readonly OPF_RESOURCE_LOAD_ONCE_ATTRIBUTE_KEY = 'opf-load-once'; protected readonly OPF_RESOURCE_ATTRIBUTE_KEY = 'data-opf-resource'; protected embedStyles(embedOptions: { @@ -87,11 +87,10 @@ export class OpfResourceLoaderService { keyValueList?.forEach((keyValue: OpfKeyValueMap) => { attributes[keyValue.key] = keyValue.value; }); - if (!attributes[this.OPF_RESOURCE_LOAD_ONCE_ATTR_KEY]) { + if (attributes?.[this.OPF_RESOURCE_LOAD_ONCE_ATTRIBUTE_KEY] === 'true') { attributes[this.OPF_RESOURCE_ATTRIBUTE_KEY] = 'true'; - } else { - delete attributes[this.OPF_RESOURCE_LOAD_ONCE_ATTR_KEY]; } + delete attributes?.[this.OPF_RESOURCE_LOAD_ONCE_ATTRIBUTE_KEY]; return attributes; } @@ -101,6 +100,7 @@ export class OpfResourceLoaderService { * The returned Promise is resolved when the script is loaded or already present. * The returned Promise is rejected when a loading error occurs. */ + protected loadScript(resource: OpfDynamicScriptResource): Promise { return new Promise((resolve, reject) => { const attributes: { [key: string]: string } = { @@ -134,6 +134,7 @@ export class OpfResourceLoaderService { * The returned Promise is resolved when the stylesheet is loaded or already present. * The returned Promise is rejected when a loading error occurs. */ + protected loadStyles(resource: OpfDynamicScriptResource): Promise { return new Promise((resolve, reject) => { if (resource.url && !this.hasStyles(resource.url)) { @@ -178,6 +179,7 @@ export class OpfResourceLoaderService { * The returned Promise is resolved when all resources are loaded. * The returned Promise is also resolved (not rejected!) immediately when any loading error occurs. */ + loadResources( scripts: OpfDynamicScriptResource[] = [], styles: OpfDynamicScriptResource[] = []