@lwrjs/loader 0.22.10 → 0.22.11
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/README.md +33 -1
- package/build/assets/prod/lwr-error-shim.js +1 -1
- package/build/assets/prod/lwr-loader-shim-legacy.bundle.js +413 -18
- package/build/assets/prod/lwr-loader-shim-legacy.bundle.min.js +3 -3
- package/build/assets/prod/lwr-loader-shim-legacy.js +217 -10
- package/build/assets/prod/lwr-loader-shim.bundle.js +6 -6
- package/build/assets/prod/lwr-loader-shim.bundle.min.js +2 -2
- package/build/assets/prod/lwr-loader-shim.js +5 -5
- package/build/cjs/modules/lwr/loaderLegacy/importMap/importMap.cjs +1 -1
- package/build/cjs/modules/lwr/loaderLegacy/importMap/importMapResolver.cjs +12 -0
- package/build/cjs/modules/lwr/loaderLegacy/importMap/utils.cjs +13 -1
- package/build/cjs/modules/lwr/loaderLegacy/utils/validation.cjs +93 -0
- package/build/modules/lwr/esmLoader/esmLoader.js +1 -1
- package/build/modules/lwr/loader/loader.js +1 -1
- package/build/modules/lwr/loaderLegacy/importMap/importMap.js +3 -2
- package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.d.ts +1 -0
- package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.js +13 -0
- package/build/modules/lwr/loaderLegacy/importMap/utils.d.ts +12 -1
- package/build/modules/lwr/loaderLegacy/importMap/utils.js +24 -2
- package/build/modules/lwr/loaderLegacy/loaderLegacy.d.ts +11 -1
- package/build/modules/lwr/loaderLegacy/loaderLegacy.js +196 -8
- package/build/modules/lwr/loaderLegacy/utils/validation.d.ts +50 -0
- package/build/modules/lwr/loaderLegacy/utils/validation.js +114 -0
- package/build/shim-legacy/shimLegacy.d.ts +14 -0
- package/build/shim-legacy/shimLegacy.js +51 -2
- package/build/types.d.ts +4 -0
- package/package.json +6 -6
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation logic for LWR.importMap() API
|
|
3
|
+
*
|
|
4
|
+
* This module provides validation functions to ensure that import map updates:
|
|
5
|
+
* 1. Do not remap protected system modules (lwc, lwr/*, etc.)
|
|
6
|
+
* 2. Do not use dangerous URL schemes (blob:, data:)
|
|
7
|
+
* 3. Have well-formed specifiers and URIs
|
|
8
|
+
*/
|
|
9
|
+
import { hasConsole } from '../utils/dom.js';
|
|
10
|
+
/**
|
|
11
|
+
* List of protected module patterns that cannot be remapped.
|
|
12
|
+
* Uses case-insensitive matching to prevent bypass attempts (e.g., HF-1027).
|
|
13
|
+
*/
|
|
14
|
+
const PROTECTED_PATTERNS = [
|
|
15
|
+
/^lwc$/i,
|
|
16
|
+
/^lwc\/v\//i,
|
|
17
|
+
/^@lwc\//i,
|
|
18
|
+
/^lightningmobileruntime\//i, // Lightning mobile runtime
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a specifier matches any protected module pattern.
|
|
22
|
+
*
|
|
23
|
+
* @param specifier - The module specifier to check
|
|
24
|
+
* @returns true if the specifier is protected, false otherwise
|
|
25
|
+
*/
|
|
26
|
+
export function isProtectedSpecifier(specifier) {
|
|
27
|
+
return PROTECTED_PATTERNS.some((pattern) => pattern.test(specifier));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates that a URI does not use dangerous URL schemes.
|
|
31
|
+
*
|
|
32
|
+
* Blocks:
|
|
33
|
+
* - blob: URLs (HF-1027 bypass prevention) - case-insensitive
|
|
34
|
+
* - data: URLs (inline code injection) - case-insensitive
|
|
35
|
+
*
|
|
36
|
+
* @param uri - The URI to validate
|
|
37
|
+
* @param specifier - The specifier being mapped (for error messages)
|
|
38
|
+
* @throws Error if the URI uses a dangerous scheme
|
|
39
|
+
*/
|
|
40
|
+
export function validateMappingUri(uri, specifier) {
|
|
41
|
+
const lowerUri = uri.toLowerCase();
|
|
42
|
+
if (lowerUri.startsWith('blob:')) {
|
|
43
|
+
throw new Error(`Cannot map ${specifier} to blob: URL`);
|
|
44
|
+
}
|
|
45
|
+
if (lowerUri.startsWith('data:')) {
|
|
46
|
+
throw new Error(`Cannot map ${specifier} to data: URL`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validates that a specifier is well-formed and not protected.
|
|
51
|
+
*
|
|
52
|
+
* @param specifier - The module specifier to validate
|
|
53
|
+
* @throws Error if the specifier is invalid or protected
|
|
54
|
+
*/
|
|
55
|
+
export function validateSpecifier(specifier) {
|
|
56
|
+
if (typeof specifier !== 'string' || specifier.length === 0) {
|
|
57
|
+
throw new Error('Specifier must be a non-empty string');
|
|
58
|
+
}
|
|
59
|
+
if (isProtectedSpecifier(specifier)) {
|
|
60
|
+
throw new Error(`Cannot remap protected module: ${specifier}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validates and converts an ImportMapUpdate to ImportMap format.
|
|
65
|
+
*
|
|
66
|
+
* Performs validation checks and then converts from:
|
|
67
|
+
* { moduleScriptURL: [moduleName1, moduleName2] }
|
|
68
|
+
* To:
|
|
69
|
+
* { imports: { moduleName1: moduleScriptURL, moduleName2: moduleScriptURL } }
|
|
70
|
+
*
|
|
71
|
+
* @param update - The ImportMapUpdate object to validate and convert
|
|
72
|
+
* @returns The converted ImportMap, or null if the update is empty
|
|
73
|
+
* @throws Error if any validation fails
|
|
74
|
+
*/
|
|
75
|
+
export function validateAndConvertImportMapUpdate(update) {
|
|
76
|
+
if (!update || typeof update !== 'object') {
|
|
77
|
+
throw new Error('LWR.importMap() requires an object argument');
|
|
78
|
+
}
|
|
79
|
+
// Check if update is empty
|
|
80
|
+
const entries = Object.entries(update);
|
|
81
|
+
if (entries.length === 0) {
|
|
82
|
+
if (hasConsole) {
|
|
83
|
+
// eslint-disable-next-line lwr/no-unguarded-apis
|
|
84
|
+
console.warn('LWR.importMap() called with empty update object');
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const convertedImports = {};
|
|
89
|
+
for (const [moduleScriptURL, moduleNames] of entries) {
|
|
90
|
+
if (!Array.isArray(moduleNames)) {
|
|
91
|
+
throw new Error('moduleNames must be an array');
|
|
92
|
+
}
|
|
93
|
+
if (!moduleScriptURL || typeof moduleScriptURL !== 'string') {
|
|
94
|
+
throw new Error('moduleScriptURL must be a string');
|
|
95
|
+
}
|
|
96
|
+
validateMappingUri(moduleScriptURL, moduleScriptURL);
|
|
97
|
+
for (const moduleName of moduleNames) {
|
|
98
|
+
validateSpecifier(moduleName);
|
|
99
|
+
if (moduleName in convertedImports) {
|
|
100
|
+
if (hasConsole) {
|
|
101
|
+
// eslint-disable-next-line lwr/no-unguarded-apis
|
|
102
|
+
console.warn(`LWR.importMap(): duplicate module "${moduleName}" — already mapped to "${convertedImports[moduleName]}", ignoring mapping to "${moduleScriptURL}"`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
convertedImports[moduleName] = moduleScriptURL;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
imports: convertedImports,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -6,6 +6,7 @@ export default class LoaderShim {
|
|
|
6
6
|
private loaderModule;
|
|
7
7
|
private defineCache;
|
|
8
8
|
private orderedDefs;
|
|
9
|
+
private importMapUpdatesCache;
|
|
9
10
|
private errorHandler?;
|
|
10
11
|
private watchdogTimerId?;
|
|
11
12
|
constructor(global: GlobalThis);
|
|
@@ -20,6 +21,19 @@ export default class LoaderShim {
|
|
|
20
21
|
* the order in which the modules were defined
|
|
21
22
|
*/
|
|
22
23
|
private tempDefine;
|
|
24
|
+
/**
|
|
25
|
+
* Create a temporary LWR.importMap() function which captures all
|
|
26
|
+
* import map updates that occur BEFORE the full loader module is available
|
|
27
|
+
*
|
|
28
|
+
* Each import map update is validated, converted to moduleName -> URL mapping,
|
|
29
|
+
* and merged into the importMapUpdatesCache with write-once protection
|
|
30
|
+
*/
|
|
31
|
+
private tempImportMap;
|
|
32
|
+
/**
|
|
33
|
+
* Apply all cached import map updates and merge with bootstrap import map
|
|
34
|
+
* Returns merged import map
|
|
35
|
+
*/
|
|
36
|
+
private getImportMappingsWithUpdates;
|
|
23
37
|
private postCustomInit;
|
|
24
38
|
private initApp;
|
|
25
39
|
private waitForBody;
|
|
@@ -4,6 +4,8 @@ import { logOperationStart, logOperationEnd } from 'lwr/profiler';
|
|
|
4
4
|
import { createLoader } from './loaderLegacy.js';
|
|
5
5
|
import { REQUIRED_MODULES_TIMEOUT } from '../shim/constants.js';
|
|
6
6
|
import { customInit } from '../shim/customInit.js';
|
|
7
|
+
import { validateAndConvertImportMapUpdate } from '../modules/lwr/loaderLegacy/utils/validation.js';
|
|
8
|
+
import { mergeImportMapEntry } from '../modules/lwr/loaderLegacy/importMap/utils.js';
|
|
7
9
|
/* eslint-disable lwr/no-unguarded-apis */
|
|
8
10
|
const hasSetTimeout = typeof setTimeout === 'function';
|
|
9
11
|
const hasConsole = typeof console !== 'undefined';
|
|
@@ -13,6 +15,7 @@ export default class LoaderShim {
|
|
|
13
15
|
constructor(global) {
|
|
14
16
|
this.defineCache = {};
|
|
15
17
|
this.orderedDefs = [];
|
|
18
|
+
this.importMapUpdatesCache = {};
|
|
16
19
|
// Start watchdog timer
|
|
17
20
|
if (hasSetTimeout) {
|
|
18
21
|
this.watchdogTimerId = this.startWatchdogTimer();
|
|
@@ -23,9 +26,11 @@ export default class LoaderShim {
|
|
|
23
26
|
this.loaderModule = 'lwr/loaderLegacy/v/__VERSION__';
|
|
24
27
|
// Set up error handler
|
|
25
28
|
this.errorHandler = this.config.onError;
|
|
26
|
-
// Set up the temporary LWR.define
|
|
29
|
+
// Set up the temporary LWR.define and LWR.importMap functions
|
|
27
30
|
const tempDefine = this.tempDefine.bind(this);
|
|
28
31
|
global.LWR.define = tempDefine;
|
|
32
|
+
const tempImportMapMethod = this.tempImportMap.bind(this);
|
|
33
|
+
global.LWR.importMap = tempImportMapMethod;
|
|
29
34
|
this.bootReady = this.config.autoBoot;
|
|
30
35
|
try {
|
|
31
36
|
this.createProfilerModule(global.LWR.define);
|
|
@@ -73,6 +78,48 @@ export default class LoaderShim {
|
|
|
73
78
|
this.initApp();
|
|
74
79
|
}
|
|
75
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a temporary LWR.importMap() function which captures all
|
|
83
|
+
* import map updates that occur BEFORE the full loader module is available
|
|
84
|
+
*
|
|
85
|
+
* Each import map update is validated, converted to moduleName -> URL mapping,
|
|
86
|
+
* and merged into the importMapUpdatesCache with write-once protection
|
|
87
|
+
*/
|
|
88
|
+
tempImportMap(importMapUpdate) {
|
|
89
|
+
try {
|
|
90
|
+
// Validate and convert the import map update to { imports: { moduleName: moduleScriptURL } }
|
|
91
|
+
const importMap = validateAndConvertImportMapUpdate(importMapUpdate);
|
|
92
|
+
// Early return if update was empty
|
|
93
|
+
if (!importMap) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Merge into cache with write-once protection
|
|
97
|
+
for (const [moduleName, moduleScriptURL] of Object.entries(importMap.imports)) {
|
|
98
|
+
mergeImportMapEntry(moduleName, moduleScriptURL, this.importMapUpdatesCache);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
this.enterErrorState(e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Apply all cached import map updates and merge with bootstrap import map
|
|
107
|
+
* Returns merged import map
|
|
108
|
+
*/
|
|
109
|
+
getImportMappingsWithUpdates() {
|
|
110
|
+
// Start with bootstrap import map
|
|
111
|
+
// Cast importMappings from object to ImportMap to access properties
|
|
112
|
+
const bootstrapMappings = this.config.importMappings;
|
|
113
|
+
// Merge with write-once protection: bootstrap mappings take precedence
|
|
114
|
+
const mergedImports = { ...(bootstrapMappings?.imports || {}) };
|
|
115
|
+
for (const [specifier, uri] of Object.entries(this.importMapUpdatesCache)) {
|
|
116
|
+
mergeImportMapEntry(specifier, uri, mergedImports);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
...(bootstrapMappings || {}),
|
|
120
|
+
imports: mergedImports,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
76
123
|
// Called by the customInit hook via lwr.initializeApp()
|
|
77
124
|
postCustomInit() {
|
|
78
125
|
this.bootReady = true;
|
|
@@ -155,10 +202,12 @@ export default class LoaderShim {
|
|
|
155
202
|
}
|
|
156
203
|
// Set up the application globals, import map, root custom element...
|
|
157
204
|
mountApp(loader) {
|
|
158
|
-
const { bootstrapModule, rootComponent,
|
|
205
|
+
const { bootstrapModule, rootComponent, rootComponents, serverData, endpoints } = this.config;
|
|
206
|
+
const importMappings = this.getImportMappingsWithUpdates();
|
|
159
207
|
// Set global LWR.define to loader.define
|
|
160
208
|
this.global.LWR = Object.freeze({
|
|
161
209
|
define: loader.define.bind(loader),
|
|
210
|
+
importMap: loader.importMap.bind(loader),
|
|
162
211
|
rootComponent,
|
|
163
212
|
rootComponents,
|
|
164
213
|
serverData: serverData || {},
|
package/build/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { ProfilerAPI, LogDispatcher } from 'lwr/profiler';
|
|
|
4
4
|
export type GlobalThis = {
|
|
5
5
|
LWR: Partial<ClientBootstrapConfig> & {
|
|
6
6
|
define: LoaderDefine;
|
|
7
|
+
importMap?: LwrImportMapUpdateMethod;
|
|
7
8
|
};
|
|
8
9
|
[key: string]: unknown;
|
|
9
10
|
};
|
|
@@ -30,6 +31,8 @@ export interface ResolvedLoader {
|
|
|
30
31
|
export type LoaderClass = {
|
|
31
32
|
new (config?: LoaderConfig): LoaderAPI;
|
|
32
33
|
};
|
|
34
|
+
export type ImportMapUpdate = Record<string, string[]>;
|
|
35
|
+
export type LwrImportMapUpdateMethod = (update: ImportMapUpdate) => void;
|
|
33
36
|
export interface BaseLoaderAPI {
|
|
34
37
|
define: LoaderDefine;
|
|
35
38
|
load(id: string): Promise<unknown>;
|
|
@@ -40,6 +43,7 @@ export interface BaseLoaderAPI {
|
|
|
40
43
|
}
|
|
41
44
|
export interface LoaderAPI extends BaseLoaderAPI {
|
|
42
45
|
registerImportMappings(mappings?: ImportMap): Promise<void>;
|
|
46
|
+
importMap: LwrImportMapUpdateMethod;
|
|
43
47
|
}
|
|
44
48
|
export type LoaderConfig = {
|
|
45
49
|
baseUrl?: string;
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"version": "0.22.
|
|
8
|
+
"version": "0.22.11",
|
|
9
9
|
"homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -61,16 +61,16 @@
|
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@locker/trusted-types": "0.26.4",
|
|
64
|
-
"@lwrjs/diagnostics": "0.22.
|
|
65
|
-
"@lwrjs/types": "0.22.
|
|
64
|
+
"@lwrjs/diagnostics": "0.22.11",
|
|
65
|
+
"@lwrjs/types": "0.22.11",
|
|
66
66
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
67
67
|
"@rollup/plugin-sucrase": "^5.0.2",
|
|
68
68
|
"@rollup/plugin-terser": "^0.4.4",
|
|
69
69
|
"rollup": "^2.80.0"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@lwrjs/client-modules": "0.22.
|
|
73
|
-
"@lwrjs/shared-utils": "0.22.
|
|
72
|
+
"@lwrjs/client-modules": "0.22.11",
|
|
73
|
+
"@lwrjs/shared-utils": "0.22.11"
|
|
74
74
|
},
|
|
75
75
|
"lwc": {
|
|
76
76
|
"modules": [
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"volta": {
|
|
91
91
|
"extends": "../../../package.json"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "85710371aa359747e08a064d4b4a2b3dfed1d30a"
|
|
94
94
|
}
|