@uekichinos/browser-gate 0.1.0
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/CHANGELOG.md +13 -0
- package/README.md +193 -0
- package/SECURITY.md +21 -0
- package/dist/index.cjs +161 -0
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.global.js +159 -0
- package/dist/index.js +134 -0
- package/package.json +66 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@uekichinos/browser-gate` are documented here.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-04-11
|
|
6
|
+
### Added
|
|
7
|
+
- Initial release
|
|
8
|
+
- `browserGate()` — detect outdated browsers and redirect or callback
|
|
9
|
+
- Feature detection mode (default) — checks globalThis, fetch, Promise.allSettled, IntersectionObserver, CSS.supports
|
|
10
|
+
- Minimum version mode — configurable per-browser version thresholds
|
|
11
|
+
- Latest version mode — live check via endoflife.date API with optional tolerance
|
|
12
|
+
- All three modes combinable — any failure triggers the outdated handler
|
|
13
|
+
- ESM, CJS, and IIFE builds
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @uekichinos/browser-gate
|
|
2
|
+
|
|
3
|
+
Detect outdated browsers and redirect or block access. Supports three detection modes — use one, two, or all three together.
|
|
4
|
+
|
|
5
|
+
- **Feature detection** (default) — checks for modern browser APIs
|
|
6
|
+
- **Minimum version** — fails if browser version is below your threshold
|
|
7
|
+
- **Latest version** — live check via [endoflife.date](https://endoflife.date) API
|
|
8
|
+
|
|
9
|
+
Zero dependencies. Works via ESM, CommonJS, or `<script>` tag.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @uekichinos/browser-gate
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
Place in `<head>` — before your app loads — so outdated browsers are caught early.
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script type="module">
|
|
27
|
+
import { browserGate } from '@uekichinos/browser-gate'
|
|
28
|
+
await browserGate({ redirect: '/outdated' })
|
|
29
|
+
</script>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
### `browserGate(options)`
|
|
37
|
+
|
|
38
|
+
Returns a `Promise<void>`. Resolves silently if the browser passes all checks. Redirects or calls `onOutdated` if it fails.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
await browserGate(options: BrowserGateOptions): Promise<void>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Options
|
|
47
|
+
|
|
48
|
+
| Option | Type | Default | Description |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `redirect` | `string` | — | URL to redirect to when outdated |
|
|
51
|
+
| `onOutdated` | `(info: OutdatedInfo) => void` | — | Callback instead of redirect |
|
|
52
|
+
| `features` | `FeatureKey[] \| true \| false` | `true` | Feature detection (see below) |
|
|
53
|
+
| `minVersions` | `{ chrome?: number, firefox?: number, safari?: number, edge?: number, opera?: number }` | — | Minimum version per browser |
|
|
54
|
+
| `checkLatest` | `boolean \| { tolerance?: number }` | — | Live latest-version check |
|
|
55
|
+
|
|
56
|
+
Either `redirect` or `onOutdated` must be provided.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Detection modes
|
|
61
|
+
|
|
62
|
+
### Mode 1 — Feature detection (default)
|
|
63
|
+
|
|
64
|
+
Runs automatically. Checks whether the browser supports five modern APIs:
|
|
65
|
+
|
|
66
|
+
| Feature | Absent in |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `globalThis` | IE11, very old browsers |
|
|
69
|
+
| `fetch` | IE11 |
|
|
70
|
+
| `Promise.allSettled` | Chrome < 76, Firefox < 71, Safari < 13 |
|
|
71
|
+
| `IntersectionObserver` | IE11, old Safari |
|
|
72
|
+
| `CSS.supports` | IE11 |
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// Default — checks all five features
|
|
76
|
+
await browserGate({ redirect: '/outdated' })
|
|
77
|
+
|
|
78
|
+
// Custom feature list
|
|
79
|
+
await browserGate({
|
|
80
|
+
redirect: '/outdated',
|
|
81
|
+
features: ['fetch', 'IntersectionObserver'],
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Disable feature detection
|
|
85
|
+
await browserGate({
|
|
86
|
+
redirect: '/outdated',
|
|
87
|
+
features: false,
|
|
88
|
+
minVersions: { chrome: 100 },
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Mode 2 — Minimum version
|
|
93
|
+
|
|
94
|
+
Checks the detected browser version against your thresholds. Only browsers you list are checked — others pass through.
|
|
95
|
+
|
|
96
|
+
Uses User-Agent parsing. Note: UA strings can be spoofed, so this is best combined with feature detection.
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
await browserGate({
|
|
100
|
+
redirect: '/outdated',
|
|
101
|
+
minVersions: {
|
|
102
|
+
chrome: 100,
|
|
103
|
+
firefox: 100,
|
|
104
|
+
safari: 15,
|
|
105
|
+
edge: 100,
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Mode 3 — Latest version (async)
|
|
111
|
+
|
|
112
|
+
Fetches live version data from [endoflife.date](https://endoflife.date) and checks whether the browser is up to date.
|
|
113
|
+
|
|
114
|
+
Use `tolerance` to allow a few versions behind (useful since Chrome releases every 4 weeks).
|
|
115
|
+
|
|
116
|
+
Fails open on network error, timeout (5s), or unrecognised browser — your users are never blocked due to an API outage.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
// Must be on latest
|
|
120
|
+
await browserGate({ redirect: '/outdated', checkLatest: true })
|
|
121
|
+
|
|
122
|
+
// Allow up to 2 versions behind
|
|
123
|
+
await browserGate({ redirect: '/outdated', checkLatest: { tolerance: 2 } })
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Combining all three modes
|
|
129
|
+
|
|
130
|
+
Any failing check triggers the outdated handler.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
await browserGate({
|
|
134
|
+
redirect: '/outdated',
|
|
135
|
+
features: ['fetch', 'IntersectionObserver', 'CSS.supports'],
|
|
136
|
+
minVersions: { chrome: 100, safari: 15 },
|
|
137
|
+
checkLatest: { tolerance: 2 },
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## `onOutdated` callback
|
|
144
|
+
|
|
145
|
+
Use `onOutdated` instead of `redirect` to handle the outdated case yourself.
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
await browserGate({
|
|
149
|
+
onOutdated: (info) => {
|
|
150
|
+
console.log(info.browser) // 'chrome'
|
|
151
|
+
console.log(info.version) // '80'
|
|
152
|
+
console.log(info.reasons)
|
|
153
|
+
// [
|
|
154
|
+
// 'Missing feature: IntersectionObserver',
|
|
155
|
+
// 'Below minimum version: chrome >= 100 (detected: 80)',
|
|
156
|
+
// ]
|
|
157
|
+
|
|
158
|
+
document.body.innerHTML = `<p>Please update your browser.</p>`
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Via `<script>` tag (no bundler)
|
|
166
|
+
|
|
167
|
+
```html
|
|
168
|
+
<script src="https://unpkg.com/@uekichinos/browser-gate/dist/index.global.js"></script>
|
|
169
|
+
<script>
|
|
170
|
+
BrowserGate.browserGate({ redirect: '/outdated' })
|
|
171
|
+
</script>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Supported browsers detected
|
|
177
|
+
|
|
178
|
+
| Browser | Detected via |
|
|
179
|
+
|---|---|
|
|
180
|
+
| Chrome | `Chrome/XX` in UA |
|
|
181
|
+
| Firefox | `Firefox/XX` in UA |
|
|
182
|
+
| Safari | `Version/XX Safari` in UA |
|
|
183
|
+
| Edge (Chromium) | `Edg/XX` in UA |
|
|
184
|
+
| Edge (legacy) | `Edge/XX` in UA |
|
|
185
|
+
| Opera | `OPR/XX` in UA |
|
|
186
|
+
|
|
187
|
+
Unrecognised browsers always pass through.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT © [uekichinos](https://www.npmjs.com/~uekichinos)
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|---------|-----------|
|
|
7
|
+
| 0.1.x | Yes |
|
|
8
|
+
|
|
9
|
+
## Reporting a Vulnerability
|
|
10
|
+
|
|
11
|
+
If you discover a security vulnerability, please **do not** open a public GitHub issue.
|
|
12
|
+
|
|
13
|
+
Instead, report it privately via GitHub:
|
|
14
|
+
[https://github.com/uekichinos/browser-gate/security/advisories/new](https://github.com/uekichinos/browser-gate/security/advisories/new)
|
|
15
|
+
|
|
16
|
+
Please include:
|
|
17
|
+
- A description of the vulnerability
|
|
18
|
+
- Steps to reproduce
|
|
19
|
+
- Potential impact
|
|
20
|
+
|
|
21
|
+
You can expect a response within **72 hours**. If the vulnerability is confirmed, a fix will be released as soon as possible and credited to you (unless you prefer to remain anonymous).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
browserGate: () => browserGate
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/detect-features.ts
|
|
28
|
+
var DEFAULT_FEATURES = [
|
|
29
|
+
"globalThis",
|
|
30
|
+
"fetch",
|
|
31
|
+
"Promise.allSettled",
|
|
32
|
+
"IntersectionObserver",
|
|
33
|
+
"CSS.supports"
|
|
34
|
+
];
|
|
35
|
+
function detectMissingFeatures(features) {
|
|
36
|
+
return features.filter((f) => !hasFeature(f));
|
|
37
|
+
}
|
|
38
|
+
function hasFeature(key) {
|
|
39
|
+
switch (key) {
|
|
40
|
+
case "globalThis":
|
|
41
|
+
return typeof globalThis !== "undefined";
|
|
42
|
+
case "fetch":
|
|
43
|
+
return typeof fetch !== "undefined";
|
|
44
|
+
case "Promise.allSettled":
|
|
45
|
+
return typeof Promise !== "undefined" && typeof Promise.allSettled === "function";
|
|
46
|
+
case "IntersectionObserver":
|
|
47
|
+
return typeof IntersectionObserver !== "undefined";
|
|
48
|
+
case "CSS.supports":
|
|
49
|
+
return typeof CSS !== "undefined" && typeof CSS.supports === "function";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/detect-latest.ts
|
|
54
|
+
var ENDOFLIFE_URLS = {
|
|
55
|
+
chrome: "https://endoflife.date/api/chrome.json",
|
|
56
|
+
firefox: "https://endoflife.date/api/firefox.json",
|
|
57
|
+
safari: "https://endoflife.date/api/safari.json",
|
|
58
|
+
edge: "https://endoflife.date/api/edge.json"
|
|
59
|
+
};
|
|
60
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
61
|
+
async function checkLatestVersion(browser, tolerance = 0) {
|
|
62
|
+
if (browser.name === "unknown" || browser.version === null) {
|
|
63
|
+
return { passed: true };
|
|
64
|
+
}
|
|
65
|
+
const url = ENDOFLIFE_URLS[browser.name];
|
|
66
|
+
if (!url) return { passed: true };
|
|
67
|
+
try {
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
70
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
if (!res.ok) return { passed: true };
|
|
73
|
+
const data = await res.json();
|
|
74
|
+
if (!Array.isArray(data) || data.length === 0) return { passed: true };
|
|
75
|
+
const latestCycle = parseInt(data[0].cycle, 10);
|
|
76
|
+
if (isNaN(latestCycle)) return { passed: true };
|
|
77
|
+
const diff = latestCycle - browser.version;
|
|
78
|
+
if (diff > tolerance) {
|
|
79
|
+
return {
|
|
80
|
+
passed: false,
|
|
81
|
+
reason: `Outdated: ${browser.name} ${browser.version} is ${diff} version(s) behind latest (${latestCycle})`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { passed: true };
|
|
85
|
+
} catch {
|
|
86
|
+
return { passed: true };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/ua-parser.ts
|
|
91
|
+
function parseUA(ua) {
|
|
92
|
+
const edgeMatch = ua.match(/Edg\/(\d+)/);
|
|
93
|
+
if (edgeMatch) return { name: "edge", version: parseInt(edgeMatch[1], 10) };
|
|
94
|
+
const edgeLegacyMatch = ua.match(/Edge\/(\d+)/);
|
|
95
|
+
if (edgeLegacyMatch) return { name: "edge", version: parseInt(edgeLegacyMatch[1], 10) };
|
|
96
|
+
const operaMatch = ua.match(/OPR\/(\d+)/);
|
|
97
|
+
if (operaMatch) return { name: "opera", version: parseInt(operaMatch[1], 10) };
|
|
98
|
+
const chromeMatch = ua.match(/Chrome\/(\d+)/);
|
|
99
|
+
if (chromeMatch) return { name: "chrome", version: parseInt(chromeMatch[1], 10) };
|
|
100
|
+
const firefoxMatch = ua.match(/Firefox\/(\d+)/);
|
|
101
|
+
if (firefoxMatch) return { name: "firefox", version: parseInt(firefoxMatch[1], 10) };
|
|
102
|
+
const safariMatch = ua.match(/Version\/(\d+).*Safari/);
|
|
103
|
+
if (safariMatch) return { name: "safari", version: parseInt(safariMatch[1], 10) };
|
|
104
|
+
return { name: "unknown", version: null };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/detect-version.ts
|
|
108
|
+
function checkMinVersions(minVersions, ua) {
|
|
109
|
+
const browser = parseUA(ua);
|
|
110
|
+
if (browser.name === "unknown" || browser.version === null) {
|
|
111
|
+
return { passed: true };
|
|
112
|
+
}
|
|
113
|
+
const min = minVersions[browser.name];
|
|
114
|
+
if (min === void 0) return { passed: true };
|
|
115
|
+
if (browser.version < min) {
|
|
116
|
+
return {
|
|
117
|
+
passed: false,
|
|
118
|
+
reason: `Below minimum version: ${browser.name} >= ${min} (detected: ${browser.version})`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { passed: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/index.ts
|
|
125
|
+
async function browserGate(options) {
|
|
126
|
+
const ua = navigator.userAgent;
|
|
127
|
+
const browser = parseUA(ua);
|
|
128
|
+
const reasons = [];
|
|
129
|
+
if (options.features !== false) {
|
|
130
|
+
const featuresToCheck = options.features === true || options.features === void 0 ? DEFAULT_FEATURES : options.features;
|
|
131
|
+
const missing = detectMissingFeatures(featuresToCheck);
|
|
132
|
+
for (const f of missing) {
|
|
133
|
+
reasons.push(`Missing feature: ${f}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (options.minVersions) {
|
|
137
|
+
const result = checkMinVersions(options.minVersions, ua);
|
|
138
|
+
if (!result.passed && result.reason) reasons.push(result.reason);
|
|
139
|
+
}
|
|
140
|
+
if (options.checkLatest) {
|
|
141
|
+
const tolerance = typeof options.checkLatest === "object" ? options.checkLatest.tolerance ?? 0 : 0;
|
|
142
|
+
const result = await checkLatestVersion(browser, tolerance);
|
|
143
|
+
if (!result.passed && result.reason) reasons.push(result.reason);
|
|
144
|
+
}
|
|
145
|
+
if (reasons.length === 0) return;
|
|
146
|
+
const info = {
|
|
147
|
+
browser: browser.name,
|
|
148
|
+
version: browser.version !== null ? String(browser.version) : null,
|
|
149
|
+
reasons
|
|
150
|
+
};
|
|
151
|
+
if (options.onOutdated) {
|
|
152
|
+
options.onOutdated(info);
|
|
153
|
+
} else if (options.redirect) {
|
|
154
|
+
window.location.href = options.redirect;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
158
|
+
0 && (module.exports = {
|
|
159
|
+
browserGate
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type BrowserName = 'chrome' | 'firefox' | 'safari' | 'edge' | 'opera';
|
|
2
|
+
type FeatureKey = 'globalThis' | 'fetch' | 'Promise.allSettled' | 'IntersectionObserver' | 'CSS.supports';
|
|
3
|
+
interface OutdatedInfo {
|
|
4
|
+
/** Detected browser name e.g. `'chrome'`, or `'unknown'` if undetected */
|
|
5
|
+
browser: string;
|
|
6
|
+
/** Detected major version e.g. `'98'`, or `null` if undetected */
|
|
7
|
+
version: string | null;
|
|
8
|
+
/** List of reasons the browser was considered outdated */
|
|
9
|
+
reasons: string[];
|
|
10
|
+
}
|
|
11
|
+
interface BrowserGateOptions {
|
|
12
|
+
/**
|
|
13
|
+
* URL to redirect to when the browser is outdated.
|
|
14
|
+
* Either `redirect` or `onOutdated` must be provided.
|
|
15
|
+
*/
|
|
16
|
+
redirect?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Called with outdated browser info instead of redirecting.
|
|
19
|
+
* Either `redirect` or `onOutdated` must be provided.
|
|
20
|
+
*/
|
|
21
|
+
onOutdated?: (info: OutdatedInfo) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Feature detection mode (default — always runs unless set to `false`).
|
|
24
|
+
* - `true` or omitted — checks the default feature set
|
|
25
|
+
* - `FeatureKey[]` — checks only the specified features
|
|
26
|
+
* - `false` — disables feature detection entirely
|
|
27
|
+
*/
|
|
28
|
+
features?: FeatureKey[] | boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Minimum version mode — fail if the detected browser version is below the threshold.
|
|
31
|
+
* Only browsers listed here are checked; others are allowed through.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* { chrome: 100, firefox: 100, safari: 15, edge: 100 }
|
|
35
|
+
*/
|
|
36
|
+
minVersions?: Partial<Record<BrowserName, number>>;
|
|
37
|
+
/**
|
|
38
|
+
* Latest version mode — fetch live data from endoflife.date and fail if
|
|
39
|
+
* the browser is behind the latest release (within optional tolerance).
|
|
40
|
+
* - `true` — zero tolerance (must be on latest)
|
|
41
|
+
* - `{ tolerance: N }` — allow up to N versions behind latest
|
|
42
|
+
*/
|
|
43
|
+
checkLatest?: boolean | {
|
|
44
|
+
tolerance?: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether the current browser is outdated and redirects or calls
|
|
50
|
+
* `onOutdated` if it is. Resolves without doing anything if the browser passes.
|
|
51
|
+
*
|
|
52
|
+
* Three detection modes — use one, two, or all three together:
|
|
53
|
+
* - **Feature detection** (default) — checks for modern browser APIs
|
|
54
|
+
* - **Minimum version** — fails if browser version is below your threshold
|
|
55
|
+
* - **Latest version** — fetches live data from endoflife.date (async)
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Default — feature detection only
|
|
59
|
+
* await browserGate({ redirect: '/outdated' })
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // All three combined
|
|
63
|
+
* await browserGate({
|
|
64
|
+
* redirect: '/outdated',
|
|
65
|
+
* minVersions: { chrome: 100, safari: 15 },
|
|
66
|
+
* checkLatest: { tolerance: 2 },
|
|
67
|
+
* })
|
|
68
|
+
*/
|
|
69
|
+
declare function browserGate(options: BrowserGateOptions): Promise<void>;
|
|
70
|
+
|
|
71
|
+
export { type BrowserGateOptions, type BrowserName, type FeatureKey, type OutdatedInfo, browserGate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type BrowserName = 'chrome' | 'firefox' | 'safari' | 'edge' | 'opera';
|
|
2
|
+
type FeatureKey = 'globalThis' | 'fetch' | 'Promise.allSettled' | 'IntersectionObserver' | 'CSS.supports';
|
|
3
|
+
interface OutdatedInfo {
|
|
4
|
+
/** Detected browser name e.g. `'chrome'`, or `'unknown'` if undetected */
|
|
5
|
+
browser: string;
|
|
6
|
+
/** Detected major version e.g. `'98'`, or `null` if undetected */
|
|
7
|
+
version: string | null;
|
|
8
|
+
/** List of reasons the browser was considered outdated */
|
|
9
|
+
reasons: string[];
|
|
10
|
+
}
|
|
11
|
+
interface BrowserGateOptions {
|
|
12
|
+
/**
|
|
13
|
+
* URL to redirect to when the browser is outdated.
|
|
14
|
+
* Either `redirect` or `onOutdated` must be provided.
|
|
15
|
+
*/
|
|
16
|
+
redirect?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Called with outdated browser info instead of redirecting.
|
|
19
|
+
* Either `redirect` or `onOutdated` must be provided.
|
|
20
|
+
*/
|
|
21
|
+
onOutdated?: (info: OutdatedInfo) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Feature detection mode (default — always runs unless set to `false`).
|
|
24
|
+
* - `true` or omitted — checks the default feature set
|
|
25
|
+
* - `FeatureKey[]` — checks only the specified features
|
|
26
|
+
* - `false` — disables feature detection entirely
|
|
27
|
+
*/
|
|
28
|
+
features?: FeatureKey[] | boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Minimum version mode — fail if the detected browser version is below the threshold.
|
|
31
|
+
* Only browsers listed here are checked; others are allowed through.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* { chrome: 100, firefox: 100, safari: 15, edge: 100 }
|
|
35
|
+
*/
|
|
36
|
+
minVersions?: Partial<Record<BrowserName, number>>;
|
|
37
|
+
/**
|
|
38
|
+
* Latest version mode — fetch live data from endoflife.date and fail if
|
|
39
|
+
* the browser is behind the latest release (within optional tolerance).
|
|
40
|
+
* - `true` — zero tolerance (must be on latest)
|
|
41
|
+
* - `{ tolerance: N }` — allow up to N versions behind latest
|
|
42
|
+
*/
|
|
43
|
+
checkLatest?: boolean | {
|
|
44
|
+
tolerance?: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether the current browser is outdated and redirects or calls
|
|
50
|
+
* `onOutdated` if it is. Resolves without doing anything if the browser passes.
|
|
51
|
+
*
|
|
52
|
+
* Three detection modes — use one, two, or all three together:
|
|
53
|
+
* - **Feature detection** (default) — checks for modern browser APIs
|
|
54
|
+
* - **Minimum version** — fails if browser version is below your threshold
|
|
55
|
+
* - **Latest version** — fetches live data from endoflife.date (async)
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Default — feature detection only
|
|
59
|
+
* await browserGate({ redirect: '/outdated' })
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // All three combined
|
|
63
|
+
* await browserGate({
|
|
64
|
+
* redirect: '/outdated',
|
|
65
|
+
* minVersions: { chrome: 100, safari: 15 },
|
|
66
|
+
* checkLatest: { tolerance: 2 },
|
|
67
|
+
* })
|
|
68
|
+
*/
|
|
69
|
+
declare function browserGate(options: BrowserGateOptions): Promise<void>;
|
|
70
|
+
|
|
71
|
+
export { type BrowserGateOptions, type BrowserName, type FeatureKey, type OutdatedInfo, browserGate };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var BrowserGate = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
browserGate: () => browserGate
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// src/detect-features.ts
|
|
28
|
+
var DEFAULT_FEATURES = [
|
|
29
|
+
"globalThis",
|
|
30
|
+
"fetch",
|
|
31
|
+
"Promise.allSettled",
|
|
32
|
+
"IntersectionObserver",
|
|
33
|
+
"CSS.supports"
|
|
34
|
+
];
|
|
35
|
+
function detectMissingFeatures(features) {
|
|
36
|
+
return features.filter((f) => !hasFeature(f));
|
|
37
|
+
}
|
|
38
|
+
function hasFeature(key) {
|
|
39
|
+
switch (key) {
|
|
40
|
+
case "globalThis":
|
|
41
|
+
return typeof globalThis !== "undefined";
|
|
42
|
+
case "fetch":
|
|
43
|
+
return typeof fetch !== "undefined";
|
|
44
|
+
case "Promise.allSettled":
|
|
45
|
+
return typeof Promise !== "undefined" && typeof Promise.allSettled === "function";
|
|
46
|
+
case "IntersectionObserver":
|
|
47
|
+
return typeof IntersectionObserver !== "undefined";
|
|
48
|
+
case "CSS.supports":
|
|
49
|
+
return typeof CSS !== "undefined" && typeof CSS.supports === "function";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/detect-latest.ts
|
|
54
|
+
var ENDOFLIFE_URLS = {
|
|
55
|
+
chrome: "https://endoflife.date/api/chrome.json",
|
|
56
|
+
firefox: "https://endoflife.date/api/firefox.json",
|
|
57
|
+
safari: "https://endoflife.date/api/safari.json",
|
|
58
|
+
edge: "https://endoflife.date/api/edge.json"
|
|
59
|
+
};
|
|
60
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
61
|
+
async function checkLatestVersion(browser, tolerance = 0) {
|
|
62
|
+
if (browser.name === "unknown" || browser.version === null) {
|
|
63
|
+
return { passed: true };
|
|
64
|
+
}
|
|
65
|
+
const url = ENDOFLIFE_URLS[browser.name];
|
|
66
|
+
if (!url) return { passed: true };
|
|
67
|
+
try {
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
70
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
if (!res.ok) return { passed: true };
|
|
73
|
+
const data = await res.json();
|
|
74
|
+
if (!Array.isArray(data) || data.length === 0) return { passed: true };
|
|
75
|
+
const latestCycle = parseInt(data[0].cycle, 10);
|
|
76
|
+
if (isNaN(latestCycle)) return { passed: true };
|
|
77
|
+
const diff = latestCycle - browser.version;
|
|
78
|
+
if (diff > tolerance) {
|
|
79
|
+
return {
|
|
80
|
+
passed: false,
|
|
81
|
+
reason: `Outdated: ${browser.name} ${browser.version} is ${diff} version(s) behind latest (${latestCycle})`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { passed: true };
|
|
85
|
+
} catch {
|
|
86
|
+
return { passed: true };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/ua-parser.ts
|
|
91
|
+
function parseUA(ua) {
|
|
92
|
+
const edgeMatch = ua.match(/Edg\/(\d+)/);
|
|
93
|
+
if (edgeMatch) return { name: "edge", version: parseInt(edgeMatch[1], 10) };
|
|
94
|
+
const edgeLegacyMatch = ua.match(/Edge\/(\d+)/);
|
|
95
|
+
if (edgeLegacyMatch) return { name: "edge", version: parseInt(edgeLegacyMatch[1], 10) };
|
|
96
|
+
const operaMatch = ua.match(/OPR\/(\d+)/);
|
|
97
|
+
if (operaMatch) return { name: "opera", version: parseInt(operaMatch[1], 10) };
|
|
98
|
+
const chromeMatch = ua.match(/Chrome\/(\d+)/);
|
|
99
|
+
if (chromeMatch) return { name: "chrome", version: parseInt(chromeMatch[1], 10) };
|
|
100
|
+
const firefoxMatch = ua.match(/Firefox\/(\d+)/);
|
|
101
|
+
if (firefoxMatch) return { name: "firefox", version: parseInt(firefoxMatch[1], 10) };
|
|
102
|
+
const safariMatch = ua.match(/Version\/(\d+).*Safari/);
|
|
103
|
+
if (safariMatch) return { name: "safari", version: parseInt(safariMatch[1], 10) };
|
|
104
|
+
return { name: "unknown", version: null };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/detect-version.ts
|
|
108
|
+
function checkMinVersions(minVersions, ua) {
|
|
109
|
+
const browser = parseUA(ua);
|
|
110
|
+
if (browser.name === "unknown" || browser.version === null) {
|
|
111
|
+
return { passed: true };
|
|
112
|
+
}
|
|
113
|
+
const min = minVersions[browser.name];
|
|
114
|
+
if (min === void 0) return { passed: true };
|
|
115
|
+
if (browser.version < min) {
|
|
116
|
+
return {
|
|
117
|
+
passed: false,
|
|
118
|
+
reason: `Below minimum version: ${browser.name} >= ${min} (detected: ${browser.version})`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { passed: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/index.ts
|
|
125
|
+
async function browserGate(options) {
|
|
126
|
+
const ua = navigator.userAgent;
|
|
127
|
+
const browser = parseUA(ua);
|
|
128
|
+
const reasons = [];
|
|
129
|
+
if (options.features !== false) {
|
|
130
|
+
const featuresToCheck = options.features === true || options.features === void 0 ? DEFAULT_FEATURES : options.features;
|
|
131
|
+
const missing = detectMissingFeatures(featuresToCheck);
|
|
132
|
+
for (const f of missing) {
|
|
133
|
+
reasons.push(`Missing feature: ${f}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (options.minVersions) {
|
|
137
|
+
const result = checkMinVersions(options.minVersions, ua);
|
|
138
|
+
if (!result.passed && result.reason) reasons.push(result.reason);
|
|
139
|
+
}
|
|
140
|
+
if (options.checkLatest) {
|
|
141
|
+
const tolerance = typeof options.checkLatest === "object" ? options.checkLatest.tolerance ?? 0 : 0;
|
|
142
|
+
const result = await checkLatestVersion(browser, tolerance);
|
|
143
|
+
if (!result.passed && result.reason) reasons.push(result.reason);
|
|
144
|
+
}
|
|
145
|
+
if (reasons.length === 0) return;
|
|
146
|
+
const info = {
|
|
147
|
+
browser: browser.name,
|
|
148
|
+
version: browser.version !== null ? String(browser.version) : null,
|
|
149
|
+
reasons
|
|
150
|
+
};
|
|
151
|
+
if (options.onOutdated) {
|
|
152
|
+
options.onOutdated(info);
|
|
153
|
+
} else if (options.redirect) {
|
|
154
|
+
window.location.href = options.redirect;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return __toCommonJS(index_exports);
|
|
158
|
+
})();
|
|
159
|
+
//# sourceMappingURL=index.global.js.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// src/detect-features.ts
|
|
2
|
+
var DEFAULT_FEATURES = [
|
|
3
|
+
"globalThis",
|
|
4
|
+
"fetch",
|
|
5
|
+
"Promise.allSettled",
|
|
6
|
+
"IntersectionObserver",
|
|
7
|
+
"CSS.supports"
|
|
8
|
+
];
|
|
9
|
+
function detectMissingFeatures(features) {
|
|
10
|
+
return features.filter((f) => !hasFeature(f));
|
|
11
|
+
}
|
|
12
|
+
function hasFeature(key) {
|
|
13
|
+
switch (key) {
|
|
14
|
+
case "globalThis":
|
|
15
|
+
return typeof globalThis !== "undefined";
|
|
16
|
+
case "fetch":
|
|
17
|
+
return typeof fetch !== "undefined";
|
|
18
|
+
case "Promise.allSettled":
|
|
19
|
+
return typeof Promise !== "undefined" && typeof Promise.allSettled === "function";
|
|
20
|
+
case "IntersectionObserver":
|
|
21
|
+
return typeof IntersectionObserver !== "undefined";
|
|
22
|
+
case "CSS.supports":
|
|
23
|
+
return typeof CSS !== "undefined" && typeof CSS.supports === "function";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/detect-latest.ts
|
|
28
|
+
var ENDOFLIFE_URLS = {
|
|
29
|
+
chrome: "https://endoflife.date/api/chrome.json",
|
|
30
|
+
firefox: "https://endoflife.date/api/firefox.json",
|
|
31
|
+
safari: "https://endoflife.date/api/safari.json",
|
|
32
|
+
edge: "https://endoflife.date/api/edge.json"
|
|
33
|
+
};
|
|
34
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
35
|
+
async function checkLatestVersion(browser, tolerance = 0) {
|
|
36
|
+
if (browser.name === "unknown" || browser.version === null) {
|
|
37
|
+
return { passed: true };
|
|
38
|
+
}
|
|
39
|
+
const url = ENDOFLIFE_URLS[browser.name];
|
|
40
|
+
if (!url) return { passed: true };
|
|
41
|
+
try {
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
44
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
if (!res.ok) return { passed: true };
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
if (!Array.isArray(data) || data.length === 0) return { passed: true };
|
|
49
|
+
const latestCycle = parseInt(data[0].cycle, 10);
|
|
50
|
+
if (isNaN(latestCycle)) return { passed: true };
|
|
51
|
+
const diff = latestCycle - browser.version;
|
|
52
|
+
if (diff > tolerance) {
|
|
53
|
+
return {
|
|
54
|
+
passed: false,
|
|
55
|
+
reason: `Outdated: ${browser.name} ${browser.version} is ${diff} version(s) behind latest (${latestCycle})`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { passed: true };
|
|
59
|
+
} catch {
|
|
60
|
+
return { passed: true };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/ua-parser.ts
|
|
65
|
+
function parseUA(ua) {
|
|
66
|
+
const edgeMatch = ua.match(/Edg\/(\d+)/);
|
|
67
|
+
if (edgeMatch) return { name: "edge", version: parseInt(edgeMatch[1], 10) };
|
|
68
|
+
const edgeLegacyMatch = ua.match(/Edge\/(\d+)/);
|
|
69
|
+
if (edgeLegacyMatch) return { name: "edge", version: parseInt(edgeLegacyMatch[1], 10) };
|
|
70
|
+
const operaMatch = ua.match(/OPR\/(\d+)/);
|
|
71
|
+
if (operaMatch) return { name: "opera", version: parseInt(operaMatch[1], 10) };
|
|
72
|
+
const chromeMatch = ua.match(/Chrome\/(\d+)/);
|
|
73
|
+
if (chromeMatch) return { name: "chrome", version: parseInt(chromeMatch[1], 10) };
|
|
74
|
+
const firefoxMatch = ua.match(/Firefox\/(\d+)/);
|
|
75
|
+
if (firefoxMatch) return { name: "firefox", version: parseInt(firefoxMatch[1], 10) };
|
|
76
|
+
const safariMatch = ua.match(/Version\/(\d+).*Safari/);
|
|
77
|
+
if (safariMatch) return { name: "safari", version: parseInt(safariMatch[1], 10) };
|
|
78
|
+
return { name: "unknown", version: null };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/detect-version.ts
|
|
82
|
+
function checkMinVersions(minVersions, ua) {
|
|
83
|
+
const browser = parseUA(ua);
|
|
84
|
+
if (browser.name === "unknown" || browser.version === null) {
|
|
85
|
+
return { passed: true };
|
|
86
|
+
}
|
|
87
|
+
const min = minVersions[browser.name];
|
|
88
|
+
if (min === void 0) return { passed: true };
|
|
89
|
+
if (browser.version < min) {
|
|
90
|
+
return {
|
|
91
|
+
passed: false,
|
|
92
|
+
reason: `Below minimum version: ${browser.name} >= ${min} (detected: ${browser.version})`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { passed: true };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/index.ts
|
|
99
|
+
async function browserGate(options) {
|
|
100
|
+
const ua = navigator.userAgent;
|
|
101
|
+
const browser = parseUA(ua);
|
|
102
|
+
const reasons = [];
|
|
103
|
+
if (options.features !== false) {
|
|
104
|
+
const featuresToCheck = options.features === true || options.features === void 0 ? DEFAULT_FEATURES : options.features;
|
|
105
|
+
const missing = detectMissingFeatures(featuresToCheck);
|
|
106
|
+
for (const f of missing) {
|
|
107
|
+
reasons.push(`Missing feature: ${f}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (options.minVersions) {
|
|
111
|
+
const result = checkMinVersions(options.minVersions, ua);
|
|
112
|
+
if (!result.passed && result.reason) reasons.push(result.reason);
|
|
113
|
+
}
|
|
114
|
+
if (options.checkLatest) {
|
|
115
|
+
const tolerance = typeof options.checkLatest === "object" ? options.checkLatest.tolerance ?? 0 : 0;
|
|
116
|
+
const result = await checkLatestVersion(browser, tolerance);
|
|
117
|
+
if (!result.passed && result.reason) reasons.push(result.reason);
|
|
118
|
+
}
|
|
119
|
+
if (reasons.length === 0) return;
|
|
120
|
+
const info = {
|
|
121
|
+
browser: browser.name,
|
|
122
|
+
version: browser.version !== null ? String(browser.version) : null,
|
|
123
|
+
reasons
|
|
124
|
+
};
|
|
125
|
+
if (options.onOutdated) {
|
|
126
|
+
options.onOutdated(info);
|
|
127
|
+
} else if (options.redirect) {
|
|
128
|
+
window.location.href = options.redirect;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
browserGate
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uekichinos/browser-gate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Detect outdated browsers and redirect or block access. Supports feature detection (default), minimum version checks, and live latest-version checks via endoflife.date — use one, two, or all three together.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"uekichinos",
|
|
7
|
+
"browser",
|
|
8
|
+
"outdated",
|
|
9
|
+
"browser-detect",
|
|
10
|
+
"browser-redirect",
|
|
11
|
+
"browser-gate",
|
|
12
|
+
"legacy-browser",
|
|
13
|
+
"browser-support",
|
|
14
|
+
"feature-detection"
|
|
15
|
+
],
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "uekichinos",
|
|
18
|
+
"url": "https://www.npmjs.com/~uekichinos"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/uekichinos/browser-gate#readme",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/uekichinos/browser-gate.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"funding": {
|
|
27
|
+
"type": "github",
|
|
28
|
+
"url": "https://github.com/sponsors/uekichinos"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "./dist/index.cjs",
|
|
32
|
+
"module": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js",
|
|
38
|
+
"require": "./dist/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist/*.js",
|
|
43
|
+
"dist/*.cjs",
|
|
44
|
+
"dist/*.d.ts",
|
|
45
|
+
"dist/*.d.cts",
|
|
46
|
+
"dist/*.global.js",
|
|
47
|
+
"CHANGELOG.md",
|
|
48
|
+
"SECURITY.md"
|
|
49
|
+
],
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"prepublishOnly": "pnpm build && pnpm test",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"lint": "tsc --noEmit"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"happy-dom": "^20.8.9",
|
|
62
|
+
"tsup": "^8.2.4",
|
|
63
|
+
"typescript": "^5.5.4",
|
|
64
|
+
"vitest": "^3.2.4"
|
|
65
|
+
}
|
|
66
|
+
}
|