Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0f50ab4
chore(deps): full package update
maxholman Dec 15, 2024
8db96a7
chore: eslint, prettier -> biome
maxholman Dec 15, 2024
290f702
chore: switch uint8 encoding libs and tidy up extraneous "CF JWT" gar…
maxholman Dec 15, 2024
681e944
refactor(tests): move fixture into their own files and organise better
maxholman Dec 15, 2024
f002be2
chore: base on node22
maxholman Dec 15, 2024
5b53a25
chore(examples): refactor typescript usage
maxholman Dec 15, 2024
fe96e1b
chore: typo
maxholman Dec 15, 2024
f1fad6e
chore: remove the base64 export, consumers should use their own that'…
maxholman Dec 15, 2024
fcc37fc
chore: lint
maxholman Dec 15, 2024
2cab8a3
chore: lint
maxholman Dec 15, 2024
98fdda2
chore: drop node18 tests
maxholman Dec 15, 2024
d390b05
chore(deps): update lockfile
maxholman Dec 15, 2024
22f6e62
chore: remove examples from main tsconfig
maxholman Dec 15, 2024
b74c8d5
chore: seo for package managers
maxholman Dec 15, 2024
8e8b860
chore: add dev script, and ensure a test is run before versioning/tag…
maxholman Dec 15, 2024
335a9d8
chore: include sourcemaps in distribution
maxholman Dec 15, 2024
c4e452e
chore: lint
maxholman Dec 15, 2024
1b1c442
chore(examples): improvements and refactoring
maxholman Dec 15, 2024
1c96c75
chore(deps): update lockfile
maxholman Dec 15, 2024
5b26f65
chore: add ignores
maxholman Dec 15, 2024
129f8cd
chore: final demise of eslint
maxholman Dec 15, 2024
39279a7
chore: initial commit
maxholman Dec 15, 2024
5bbf5ba
chore: replace biome with oxlint and oxfmt
maxholman Sep 3, 2026
aa58334
chore(deps): update everything to 2026 versions
maxholman Sep 3, 2026
db9a0b7
chore(tsconfig): fix project references and drop the root solution file
maxholman Sep 3, 2026
f250490
feat!: send RFC 8291 aes128gcm with RFC 8292 vapid auth
maxholman Sep 3, 2026
a0c3f4c
chore(examples): update for the new dependency majors
maxholman Sep 3, 2026
9483bc3
docs: add a root README and describe the 2.0 encoding
maxholman Sep 3, 2026
6b1634a
docs: drop an ambiguous comment on the Jsonifiable type
maxholman Sep 3, 2026
4877531
test(e2e): decrypt a real push in chrome and firefox
maxholman Sep 3, 2026
1754926
fix(types): declare the payload body as ArrayBuffer backed
maxholman Sep 3, 2026
b67f694
refactor(e2e): drop vitest, the global augmentation and the hand roll…
maxholman Sep 3, 2026
2adc032
chore(deps): share common versions through a pnpm catalog
maxholman Sep 3, 2026
1a2b75b
refactor(e2e): use the playwright test runner
maxholman Sep 3, 2026
c53c021
test(e2e): fail on console errors, warnings and uncaught errors
maxholman Sep 3, 2026
565c7d2
fix(test): pin the mocked clock to UTC
maxholman Sep 3, 2026
541b742
ci: limit the github token to reading contents
maxholman Sep 3, 2026
6b9cb09
docs: install with pnpm or npm, and name the package up front
maxholman Sep 3, 2026
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
6 changes: 0 additions & 6 deletions .eslintignore

This file was deleted.

49 changes: 0 additions & 49 deletions .eslintrc.cjs

This file was deleted.

10 changes: 7 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ on:
release:
types: [published]

# npm is authenticated with NODE_AUTH_TOKEN, not the github token
permissions:
contents: read

jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
Expand Down
30 changes: 26 additions & 4 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,45 @@ on:
pull_request:
branches: ['canary', 'master']

# checkout is all either job does with the token
permissions:
contents: read

jobs:
test:
strategy:
matrix:
node-version: ['18', '20', '22']
node-version: ['22', '24', '26']

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
node-version: ${{ matrix.node-version }}

- run: make test

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v7
with:
cache: 'pnpm'
node-version-file: '.node-version'

- run: pnpm install

- run: pnpm exec oxlint

- run: pnpm exec oxfmt --check
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
node_modules
dist
coverage
test-results

.wrangler

*.tsbuildinfo

sqlite.db
**/migrations/meta/
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20
26
19 changes: 19 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"ignorePatterns": [
"**/dist",
"**/node_modules",
"**/.wrangler",
"**/dev-dist",
"**/*.html"
],
"sortPackageJson": {
"sortScripts": true
},
"sortImports": {
"newlinesBetween": false
}
}
22 changes: 22 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "promise", "import", "oxc"],
"categories": {
"correctness": "error",
"suspicious": "warn",
"perf": "warn"
},
"rules": {
"typescript/no-non-null-assertion": "error",
"no-nested-ternary": "error",
"unicorn/no-nested-ternary": "error",
"import/first": "error",
"import/no-duplicates": "error",
"import/no-unassigned-import": ["warn", { "allow": ["**/*.css"] }],
"import/no-named-as-default": "error",
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"unicorn/consistent-function-scoping": "warn",
"curly": ["error", "all"]
},
"ignorePatterns": ["**/dist", "**/dev-dist", "**/.wrangler"]
}
1 change: 0 additions & 1 deletion .prettierignore

This file was deleted.

28 changes: 15 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,36 @@

.PHONY: build
build: node_modules
pnpm exec tsc -b
pnpm exec tsc -b packages/web-push

.PHONY: dev
dev: node_modules
pnpm exec tsc -b -w
pnpm exec tsc -b packages/web-push -w

.PHONY: clean
clean: node_modules
pnpm exec tsc -b --clean
pnpm exec tsc -b packages/web-push --clean
rm -rf packages/web-push/dist

.PHONY: distclean
distclean: clean
rm -rf node_modules

.PHONY: lint
lint: node_modules
pnpm exec eslint .
pnpm exec prettier --check .

node_modules: package.json
pnpm install

.PHONY: test
test: node_modules
pnpm run -r test

.PHONY: pretty
pretty: node_modules
pnpm exec eslint --fix . || true
pnpm exec prettier --write .
pnpx sort-package-json package.json packages/*/package.json examples/*/package.json
.PHONY: e2e
e2e: node_modules build
pnpm --filter e2e run e2e

.PHONY: format
format: node_modules
pnpm exec oxfmt

.PHONY: lint
lint: node_modules
pnpm exec oxlint --fix
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# @block65/webcrypto-web-push

Send notifications using the Web Push Protocol and Web Crypto APIs.

This repository holds the package, its examples and its end to end tests.
Installation and usage are documented in the
[package README](./packages/web-push/README.md).

## Packages

| Package | Description |
| ------------------------------------------ | ---------------------------------------------- |
| [`packages/web-push`](./packages/web-push) | The published library |
| [`e2e`](./e2e) | Real browsers against their real push services |

## Examples

| Example | Description |
| -------------------------------------------------------------- | --------------------------------------------------- |
| [`examples/browser`](./examples/browser) | Vite and React app that subscribes to notifications |
| [`examples/cloudflare-workers`](./examples/cloudflare-workers) | Hono worker that stores subscriptions in D1 |
| [`examples/nodejs`](./examples/nodejs) | Hono server that stores subscriptions in SQLite |

## Development

```
make # build
make test # typecheck, unit tests and smoketest
make e2e # real browsers against real push services, see e2e/README.md
make lint # oxlint
make format # oxfmt
```

## License

MIT. See [LICENSE.md](./packages/web-push/LICENSE.md).
29 changes: 29 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# e2e

Subscribes a real browser to its real push service, sends a notification with
`@block65/webcrypto-web-push`, and asserts the service worker decrypted it. A
`201` from a push service only means the request was accepted, so this is the
only test that shows a subscriber can read what the library produces.

It is not part of `make test` and does not run in CI: it needs the network, and
a subscription that can expire.

```
make e2e
```

## Requirements

Chrome comes from the system rather than playwright, because playwright's
chromium is built without the credentials it needs to register with FCM. The
push API is also unavailable in incognito, which is what a browser context
gives you unless it is persistent.

```
pnpm exec playwright install firefox
```

A browser that is not installed skips rather than fails.

The playwright runner drives it, so `pnpm exec playwright test --ui` and the
usual reporters work.
25 changes: 25 additions & 0 deletions e2e/browsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { chromium, firefox, type BrowserContext } from '@playwright/test';

type Launch = (profileDir: string) => Promise<BrowserContext>;

export const browsers: Record<string, Launch> = {
// playwright's bundled chromium has no FCM credentials, so this needs a real
// chrome install. the push api is also unavailable in incognito, which is
// what a non-persistent context gives you
chrome: (profileDir) =>
chromium.launchPersistentContext(profileDir, {
channel: 'chrome',
permissions: ['notifications'],
}),

firefox: (profileDir) =>
firefox.launchPersistentContext(profileDir, {
permissions: ['notifications'],
firefoxUserPrefs: {
'dom.push.enabled': true,
'dom.push.connection.enabled': true,
'dom.push.serverURL': 'wss://push.services.mozilla.com/',
'permissions.default.desktop-notification': 1,
},
}),
};
17 changes: 17 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "e2e",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"e2e": "playwright test"
},
"devDependencies": {
"@block65/webcrypto-web-push": "workspace:*",
"@hono/node-server": "catalog:",
"@playwright/test": "^1.62.1",
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"hono": "catalog:"
}
}
7 changes: 7 additions & 0 deletions e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
// a real subscription and delivery, over the network
timeout: 120_000,
reporter: 'list',
});
18 changes: 18 additions & 0 deletions e2e/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>waiting</title>
<!-- stops the browser requesting one and logging a 404 -->
<link rel="icon" href="data:," />
</head>
<body>
<script>
// the title is how the test observes what the worker decrypted. it has to
// be listening before anything is sent
navigator.serviceWorker.addEventListener('message', (event) => {
document.title = event.data;
});
</script>
</body>
</html>
17 changes: 17 additions & 0 deletions e2e/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
self.addEventListener('push', (event) => {
const text = event.data ? event.data.text() : '';

event.waitUntil(
(async () => {
const clients = await self.clients.matchAll({
includeUncontrolled: true,
type: 'window',
});

for (const client of clients) {
// oxlint-disable-next-line unicorn/require-post-message-target-origin -- Client.postMessage has no origin argument
client.postMessage(text);
}
})(),
);
});
Loading