Skip to content

Commit b370586

Browse files
authored
feat: use TrustedTypes policy to create script URL (#1217)
Introduces using of trusted types to create the script url for loading the maps API if supported and allowed by the CSP. Added documentation on using `@googlemaps/js-api-loader` in CSP environments with Trusted Types enabled. Updated TypeScript configuration to include `trusted-types`.
1 parent 9bdc3f3 commit b370586

9 files changed

Lines changed: 114 additions & 15 deletions

File tree

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,28 @@ The following libraries are available:
153153
- `addressValidation`: [`google.maps.AddressValidationLibrary`](https://developers.google.com/maps/documentation/javascript/reference/library-interfaces#AddressValidationLibrary)
154154
- `drawing`: [`google.maps.DrawingLibrary`](https://developers.google.com/maps/documentation/javascript/reference/library-interfaces#DrawingLibrary) (deprecated)
155155

156+
### Content Security Policy and Trusted Types
157+
158+
The loader supports pages that enforce
159+
[`require-trusted-types-for 'script'`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for).
160+
When Trusted Types are available, the loader creates a policy named
161+
`@googlemaps/js-api-loader` and uses it to assign the Google Maps JavaScript API
162+
script URL.
163+
164+
If your page uses a `trusted-types` CSP directive, allow this policy name:
165+
166+
```http
167+
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types @googlemaps/js-api-loader google-maps-api-loader google-maps-api#html lit-html
168+
```
169+
170+
`@googlemaps/js-api-loader` is used by this package. `google-maps-api-loader`
171+
`google-maps-api#html`, and `lit-html` are used by the Maps JavaScript API
172+
script internally during execution.
173+
174+
If the policy name is not allowed, the loader logs a development warning and
175+
falls back to assigning a string URL. On pages that enforce
176+
`require-trusted-types-for 'script'`, the browser will block that fallback.
177+
156178
## Migrating from v1 to v2
157179

158180
See the [migration guide](MIGRATION.md).

eslint.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ export default defineConfig(
2828
"@typescript-eslint/ban-ts-comment": "off",
2929
"@typescript-eslint/no-empty-function": "warn",
3030
"@typescript-eslint/member-ordering": "warn",
31+
"@typescript-eslint/no-unused-vars": [
32+
"error",
33+
{
34+
argsIgnorePattern: "^_",
35+
varsIgnorePattern: "^_",
36+
caughtErrorsIgnorePattern: "^_",
37+
},
38+
],
3139
"@typescript-eslint/explicit-member-accessibility": [
3240
"warn",
3341
{

package-lock.json

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"scripts": {
3535
"prepack": "npm run build",
3636
"lint": "eslint .",
37-
"test": "npm run lint && npm run test:unit && npm run test:bundlers",
37+
"typecheck": "tsc --noEmit",
38+
"test": "npm run lint && npm run typecheck && npm run test:unit && npm run test:bundlers",
3839
"test:unit": "NODE_OPTIONS='--experimental-vm-modules --disable-warning=ExperimentalWarning' jest ./src",
3940
"test:bundlers": "cd test-bundlers && ./test-all.sh",
4041
"build": "rm -rf ./dist && rollup -c",
@@ -53,6 +54,7 @@
5354
"@rollup/plugin-terser": "^1.0.0",
5455
"@rollup/plugin-typescript": "^12.1.0",
5556
"@types/jest": "^30.0.0",
57+
"@types/trusted-types": "^2.0.7",
5658
"@typescript-eslint/eslint-plugin": "^8.42.0",
5759
"@typescript-eslint/parser": "^8.42.0",
5860
"core-js": "^3.6.4",
@@ -67,7 +69,7 @@
6769
"prettier": "^3.0.3",
6870
"rollup": "^4.6.1",
6971
"rollup-plugin-dts": "^6.2.3",
70-
"ts-jest": "^29.1.1",
72+
"ts-jest": "^29.4.6",
7173
"tslib": "^2.8.1",
7274
"typescript": "^6.0.3",
7375
"typescript-eslint": "^8.42.0"

src/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ beforeEach(() => {
4949
jest.resetModules();
5050
jest.clearAllMocks();
5151

52-
delete globalThis.google;
52+
delete (globalThis as { google?: unknown }).google;
5353
});
5454

5555
describe("importLibrary(): basic operation", () => {

src/messages.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ export const MSG_API_KEY_USED =
4848
"The 'apiKey' parameter was used in setOptions(), but 'key' is the correct " +
4949
"parameter name. Please update your configuration.";
5050

51+
export const MSG_TRUSTED_TYPES_POLICY_FAILED = (policyName: string, error: unknown) =>
52+
`Failed to create Trusted Types policy "${policyName}": ${error instanceof Error ? error.message : String(error)}.\n\n` +
53+
`If your Content Security Policy uses "require-trusted-types-for 'script'", ` +
54+
`allow this policy with "trusted-types ${policyName} google-maps-api-loader google-maps-api#html lit-html". ` +
55+
`The "google-maps-api-loader", "lit-html", and "google-maps-api#html" policies are required for full Maps JavaScript API execution. ` +
56+
`Falling back to a string script URL.`;
57+
5158
// Development mode check - bundlers will replace process.env.NODE_ENV at build time
5259
declare const process: { env: { NODE_ENV?: string } };
5360
const __DEV__ = process.env.NODE_ENV !== 'production';

src/setScriptSrc.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,40 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
export function setScriptSrc(script: HTMLScriptElement, src: string) {
7-
script.src = src;
6+
import type { TrustedTypePolicyFactory } from "trusted-types";
7+
8+
import { logDevWarning, MSG_TRUSTED_TYPES_POLICY_FAILED } from "./messages.js";
9+
10+
const TRUSTED_TYPES_POLICY_NAME = "@googlemaps/js-api-loader";
11+
type TrustedTypesWindow = Window & {
12+
trustedTypes?: TrustedTypePolicyFactory;
13+
};
14+
15+
// Try to create a Trusted Types policy when supported. Falls back to a string
16+
// passthrough when Trusted Types is unsupported, blocked by CSP, or already
17+
// registered.
18+
19+
let policy: {
20+
createScriptURL: (url: string) => string | TrustedScriptURL;
21+
};
22+
23+
const trustedTypes = (window as TrustedTypesWindow).trustedTypes;
24+
25+
if (!trustedTypes) {
26+
policy = { createScriptURL: (url: string) => url };
27+
} else {
28+
try {
29+
policy = trustedTypes.createPolicy(TRUSTED_TYPES_POLICY_NAME, {
30+
createScriptURL: (url: string) => url,
31+
});
32+
} catch (e) {
33+
logDevWarning(
34+
MSG_TRUSTED_TYPES_POLICY_FAILED(TRUSTED_TYPES_POLICY_NAME, e)
35+
);
36+
policy = { createScriptURL: (url: string) => url };
37+
}
38+
}
39+
40+
export function setScriptSrc(script: HTMLScriptElement, src: string): void {
41+
script.src = policy.createScriptURL(src) as string;
842
}

test-bundlers/test-all.sh

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ echo ""
2929
for bundler in vite webpack rollup ; do
3030
dir="$SCRIPT_DIR/${bundler}-test"
3131

32+
echo " Installing dependencies for $bundler..."
3233
(
33-
cd $dir
34-
# Install dependencies
35-
echo " Installing dependencies for $bundler..."
36-
npm install --silent
37-
npm install --silent --no-save "../../$TARBALL"
38-
)
34+
cd "$dir" || exit 1
35+
npm install --silent || { echo "Failed to install dependencies for $bundler"; exit 1; }
36+
npm install --silent --no-save "../../$TARBALL" || { echo "Failed to install tarball for $bundler"; exit 1; }
37+
) || exit 1
3938
done
4039

4140
echo ""
@@ -131,8 +130,25 @@ echo ""
131130
echo "======================================"
132131
if [ $NUM_FAILED -eq 0 ]; then
133132
echo -e "${GREEN}✓ All bundler tests passed!${NC}"
134-
exit 0
135133
else
136134
echo -e "${RED}$NUM_FAILED bundler test(s) failed${NC}"
135+
fi
136+
137+
# Cleanup
138+
echo ""
139+
echo "🧹 Cleaning up..."
140+
for bundler in vite webpack rollup ; do
141+
dir="$SCRIPT_DIR/${bundler}-test"
142+
echo " Cleaning $bundler-test..."
143+
(cd "$dir" && git clean -qfdx)
144+
done
145+
146+
cd "$ROOT_DIR"
147+
rm -f "$TARBALL"
148+
echo " Removed $TARBALL"
149+
150+
if [ $NUM_FAILED -eq 0 ]; then
151+
exit 0
152+
else
137153
exit 1
138154
fi

tsconfig.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
"sourceMap": true,
66
"esModuleInterop": true,
77
"isolatedModules": true,
8+
"skipLibCheck": true,
89
"lib": ["DOM", "DOM.Iterable", "ESNext"],
910
"target": "es2020",
1011
"module": "NodeNext",
1112
"moduleResolution": "NodeNext",
12-
"types": ["google.maps", "jest"]
13+
"typeRoots": ["./node_modules/@types"],
14+
"types": ["google.maps", "jest", "trusted-types"]
1315
},
14-
"include": ["src/**/*", "e2e/**/*.ts"],
16+
"include": ["src/**/*"],
1517
"exclude": ["node_modules", "./dist"]
1618
}

0 commit comments

Comments
 (0)