Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,28 @@ The following libraries are available:
- `addressValidation`: [`google.maps.AddressValidationLibrary`](https://developers.google.com/maps/documentation/javascript/reference/library-interfaces#AddressValidationLibrary)
- `drawing`: [`google.maps.DrawingLibrary`](https://developers.google.com/maps/documentation/javascript/reference/library-interfaces#DrawingLibrary) (deprecated)

### Content Security Policy and Trusted Types

The loader supports pages that enforce
[`require-trusted-types-for 'script'`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for).
When Trusted Types are available, the loader creates a policy named
`@googlemaps/js-api-loader` and uses it to assign the Google Maps JavaScript API
script URL.

If your page uses a `trusted-types` CSP directive, allow this policy name:

```http
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types @googlemaps/js-api-loader google-maps-api-loader google-maps-api#html lit-html
```

`@googlemaps/js-api-loader` is used by this package. `google-maps-api-loader`
`google-maps-api#html`, and `lit-html` are used by the Maps JavaScript API
script internally during execution.

If the policy name is not allowed, the loader logs a development warning and
falls back to assigning a string URL. On pages that enforce
`require-trusted-types-for 'script'`, the browser will block that fallback.

## Migrating from v1 to v2

See the [migration guide](MIGRATION.md).
Expand Down
8 changes: 8 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export default defineConfig(
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "warn",
"@typescript-eslint/member-ordering": "warn",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
"@typescript-eslint/explicit-member-accessibility": [
"warn",
{
Expand Down
10 changes: 9 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"scripts": {
"prepack": "npm run build",
"lint": "eslint .",
"test": "npm run lint && npm run test:unit && npm run test:bundlers",
"typecheck": "tsc --noEmit",
"test": "npm run lint && npm run typecheck && npm run test:unit && npm run test:bundlers",
"test:unit": "NODE_OPTIONS='--experimental-vm-modules --disable-warning=ExperimentalWarning' jest ./src",
"test:bundlers": "cd test-bundlers && ./test-all.sh",
"build": "rm -rf ./dist && rollup -c",
Expand All @@ -53,6 +54,7 @@
"@rollup/plugin-terser": "^1.0.0",
"@rollup/plugin-typescript": "^12.1.0",
"@types/jest": "^30.0.0",
"@types/trusted-types": "^2.0.7",
"@typescript-eslint/eslint-plugin": "^8.42.0",
"@typescript-eslint/parser": "^8.42.0",
"core-js": "^3.6.4",
Expand All @@ -67,7 +69,7 @@
"prettier": "^3.0.3",
"rollup": "^4.6.1",
"rollup-plugin-dts": "^6.2.3",
"ts-jest": "^29.1.1",
"ts-jest": "^29.4.6",
"tslib": "^2.8.1",
"typescript": "^6.0.3",
"typescript-eslint": "^8.42.0"
Expand Down
2 changes: 1 addition & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();

delete globalThis.google;
delete (globalThis as { google?: unknown }).google;
});

describe("importLibrary(): basic operation", () => {
Expand Down
7 changes: 7 additions & 0 deletions src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ export const MSG_API_KEY_USED =
"The 'apiKey' parameter was used in setOptions(), but 'key' is the correct " +
"parameter name. Please update your configuration.";

export const MSG_TRUSTED_TYPES_POLICY_FAILED = (policyName: string, error: unknown) =>
`Failed to create Trusted Types policy "${policyName}": ${error instanceof Error ? error.message : String(error)}.\n\n` +
`If your Content Security Policy uses "require-trusted-types-for 'script'", ` +
`allow this policy with "trusted-types ${policyName} google-maps-api-loader google-maps-api#html lit-html". ` +
`The "google-maps-api-loader", "lit-html", and "google-maps-api#html" policies are required for full Maps JavaScript API execution. ` +
`Falling back to a string script URL.`;

// Development mode check - bundlers will replace process.env.NODE_ENV at build time
declare const process: { env: { NODE_ENV?: string } };
const __DEV__ = process.env.NODE_ENV !== 'production';
Expand Down
38 changes: 36 additions & 2 deletions src/setScriptSrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@
* SPDX-License-Identifier: Apache-2.0
*/

export function setScriptSrc(script: HTMLScriptElement, src: string) {
script.src = src;
import type { TrustedTypePolicyFactory } from "trusted-types";

import { logDevWarning, MSG_TRUSTED_TYPES_POLICY_FAILED } from "./messages.js";

const TRUSTED_TYPES_POLICY_NAME = "@googlemaps/js-api-loader";
type TrustedTypesWindow = Window & {
trustedTypes?: TrustedTypePolicyFactory;
};

// Try to create a Trusted Types policy when supported. Falls back to a string
// passthrough when Trusted Types is unsupported, blocked by CSP, or already
// registered.

let policy: {
createScriptURL: (url: string) => string | TrustedScriptURL;
};

const trustedTypes = (window as TrustedTypesWindow).trustedTypes;

if (!trustedTypes) {
policy = { createScriptURL: (url: string) => url };
} else {
try {
policy = trustedTypes.createPolicy(TRUSTED_TYPES_POLICY_NAME, {
createScriptURL: (url: string) => url,
});
} catch (e) {
logDevWarning(
MSG_TRUSTED_TYPES_POLICY_FAILED(TRUSTED_TYPES_POLICY_NAME, e)
);
policy = { createScriptURL: (url: string) => url };
}
}

export function setScriptSrc(script: HTMLScriptElement, src: string): void {
script.src = policy.createScriptURL(src) as string;
}
30 changes: 23 additions & 7 deletions test-bundlers/test-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ echo ""
for bundler in vite webpack rollup ; do
dir="$SCRIPT_DIR/${bundler}-test"

echo " Installing dependencies for $bundler..."
(
cd $dir
# Install dependencies
echo " Installing dependencies for $bundler..."
npm install --silent
npm install --silent --no-save "../../$TARBALL"
)
cd "$dir" || exit 1
npm install --silent || { echo "Failed to install dependencies for $bundler"; exit 1; }
npm install --silent --no-save "../../$TARBALL" || { echo "Failed to install tarball for $bundler"; exit 1; }
) || exit 1
done

echo ""
Expand Down Expand Up @@ -131,8 +130,25 @@ echo ""
echo "======================================"
if [ $NUM_FAILED -eq 0 ]; then
echo -e "${GREEN}✓ All bundler tests passed!${NC}"
exit 0
else
echo -e "${RED}✗ $NUM_FAILED bundler test(s) failed${NC}"
fi

# Cleanup
echo ""
echo "🧹 Cleaning up..."
for bundler in vite webpack rollup ; do
dir="$SCRIPT_DIR/${bundler}-test"
echo " Cleaning $bundler-test..."
(cd "$dir" && git clean -qfdx)
done

cd "$ROOT_DIR"
rm -f "$TARBALL"
echo " Removed $TARBALL"

if [ $NUM_FAILED -eq 0 ]; then
exit 0
else
exit 1
fi
6 changes: 4 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
"sourceMap": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"target": "es2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["google.maps", "jest"]
"typeRoots": ["./node_modules/@types"],
"types": ["google.maps", "jest", "trusted-types"]
},
"include": ["src/**/*", "e2e/**/*.ts"],
"include": ["src/**/*"],
"exclude": ["node_modules", "./dist"]
}
Loading