@qr-bistro/devtools-guard 2.4.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/dist/chunk-OKFPKHFA.mjs +608 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +643 -0
- package/dist/index.mjs +19 -0
- package/dist/next.d.mts +5 -0
- package/dist/next.d.ts +5 -0
- package/dist/next.js +570 -0
- package/dist/next.mjs +19 -0
- package/dist/types-Db38Wwbv.d.mts +44 -0
- package/dist/types-Db38Wwbv.d.ts +44 -0
- package/package.json +44 -0
package/dist/next.js
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
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/next.ts
|
|
22
|
+
var next_exports = {};
|
|
23
|
+
__export(next_exports, {
|
|
24
|
+
DevtoolsGuardScript: () => DevtoolsGuardScript
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(next_exports);
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
|
|
29
|
+
// src/utils.ts
|
|
30
|
+
function isBrowser() {
|
|
31
|
+
return typeof window !== "undefined";
|
|
32
|
+
}
|
|
33
|
+
function isTouchDevice() {
|
|
34
|
+
if (!isBrowser()) return false;
|
|
35
|
+
const coarsePointer = typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
|
|
36
|
+
const hasTouch = "ontouchstart" in window || typeof navigator !== "undefined" && navigator.maxTouchPoints > 0;
|
|
37
|
+
return coarsePointer || hasTouch;
|
|
38
|
+
}
|
|
39
|
+
function resolvePrefix(prefix) {
|
|
40
|
+
return prefix.toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
function prefixKey(prefix, base) {
|
|
43
|
+
return `${resolvePrefix(prefix)}${base}`;
|
|
44
|
+
}
|
|
45
|
+
function styleObjectToCss(style) {
|
|
46
|
+
return Object.entries(style).map(([key, value]) => {
|
|
47
|
+
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
48
|
+
return `${cssKey}:${value}`;
|
|
49
|
+
}).join(";");
|
|
50
|
+
}
|
|
51
|
+
function randomMinorVersion() {
|
|
52
|
+
const major = Math.floor(Math.random() * 4) + 1;
|
|
53
|
+
const minor = Math.floor(Math.random() * 10);
|
|
54
|
+
const patch = Math.floor(Math.random() * 20);
|
|
55
|
+
return `${major}.${minor}.${patch}`;
|
|
56
|
+
}
|
|
57
|
+
var DEFAULT_DURATION = {
|
|
58
|
+
soft: 12 * 60 * 60 * 1e3,
|
|
59
|
+
// 12 hours
|
|
60
|
+
hard: 7 * 24 * 60 * 60 * 1e3
|
|
61
|
+
// 7 days
|
|
62
|
+
};
|
|
63
|
+
var DETECTION_INTERVAL = 3e3;
|
|
64
|
+
var VALIDATION_INTERVAL = 5e3;
|
|
65
|
+
var DECOY_REFRESH_INTERVAL = 6e4;
|
|
66
|
+
var WINDOW_SIZE_THRESHOLD = 160;
|
|
67
|
+
var DEBUGGER_THRESHOLD = 100;
|
|
68
|
+
|
|
69
|
+
// src/hash.ts
|
|
70
|
+
var SALT = "dtg_v2_salt_2026";
|
|
71
|
+
function cyrb53(str, seed = 0) {
|
|
72
|
+
let h1 = 3735928559 ^ seed;
|
|
73
|
+
let h2 = 1103547991 ^ seed;
|
|
74
|
+
for (let i = 0; i < str.length; i++) {
|
|
75
|
+
const ch = str.charCodeAt(i);
|
|
76
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
77
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
78
|
+
}
|
|
79
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
80
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
81
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
82
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
83
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
|
|
84
|
+
}
|
|
85
|
+
function generateTrapHash(keyName, secret) {
|
|
86
|
+
return cyrb53(`${keyName}:${secret}:${SALT}`);
|
|
87
|
+
}
|
|
88
|
+
function generateSecretHash(secret) {
|
|
89
|
+
return cyrb53(`${secret}:${SALT}:verify`, 42);
|
|
90
|
+
}
|
|
91
|
+
function disguiseValue(keyName, hash) {
|
|
92
|
+
const base = keyName.split("_").pop() || keyName;
|
|
93
|
+
switch (base) {
|
|
94
|
+
case "cache": {
|
|
95
|
+
const major = parseInt(hash.slice(0, 2), 16) % 5 + 1;
|
|
96
|
+
const minor = parseInt(hash.slice(2, 4), 16) % 10;
|
|
97
|
+
const patch = parseInt(hash.slice(4, 6), 16) % 20;
|
|
98
|
+
return `${major}.${minor}.${patch}-${hash.slice(0, 8)}`;
|
|
99
|
+
}
|
|
100
|
+
case "pref": {
|
|
101
|
+
const fakeJson = JSON.stringify({ theme: "default", lang: "en", v: hash.slice(0, 8) });
|
|
102
|
+
return btoa(fakeJson);
|
|
103
|
+
}
|
|
104
|
+
case "meta": {
|
|
105
|
+
const fakeTsBase = parseInt(hash.slice(0, 8), 16) % 1e12 + 17e11;
|
|
106
|
+
return `${fakeTsBase}|web|desktop|${hash.slice(0, 6)}`;
|
|
107
|
+
}
|
|
108
|
+
case "theme": {
|
|
109
|
+
return `dark|#1a1a2e|#e74c3c|${hash.slice(0, 6)}`;
|
|
110
|
+
}
|
|
111
|
+
case "tid": {
|
|
112
|
+
const n1 = parseInt(hash.slice(0, 4), 16) % 99999999;
|
|
113
|
+
const n2 = parseInt(hash.slice(4, 6), 16) % 9 + 1;
|
|
114
|
+
return `UA-${n1}-${n2}`;
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
return hash;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/storage.ts
|
|
122
|
+
function getLS(key) {
|
|
123
|
+
if (!isBrowser()) return null;
|
|
124
|
+
try {
|
|
125
|
+
return localStorage.getItem(key);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function setLS(key, value) {
|
|
131
|
+
if (!isBrowser()) return;
|
|
132
|
+
try {
|
|
133
|
+
localStorage.setItem(key, value);
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function removeLS(key) {
|
|
138
|
+
if (!isBrowser()) return;
|
|
139
|
+
try {
|
|
140
|
+
localStorage.removeItem(key);
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function setSS(key, value) {
|
|
145
|
+
if (!isBrowser()) return;
|
|
146
|
+
try {
|
|
147
|
+
sessionStorage.setItem(key, value);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function setCookie(name, value, days = 365) {
|
|
152
|
+
if (!isBrowser()) return;
|
|
153
|
+
try {
|
|
154
|
+
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
|
155
|
+
document.cookie = `${name}=${encodeURIComponent(value)};expires=${expires};path=/;SameSite=Lax`;
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
var FP_SESSION_BASE = "_s_init";
|
|
160
|
+
var FP_COOKIE_BASE = "_p_id";
|
|
161
|
+
var FP_LOCAL_BASE = "_l_init";
|
|
162
|
+
function setFingerprint(prefix, value) {
|
|
163
|
+
setSS(prefixKey(prefix, FP_SESSION_BASE), value);
|
|
164
|
+
setCookie(prefixKey(prefix, FP_COOKIE_BASE), value);
|
|
165
|
+
setLS(prefixKey(prefix, FP_LOCAL_BASE), value);
|
|
166
|
+
}
|
|
167
|
+
function hasLocalFingerprint(prefix) {
|
|
168
|
+
return getLS(prefixKey(prefix, FP_LOCAL_BASE)) !== null;
|
|
169
|
+
}
|
|
170
|
+
var BAN_STATE_BASE = "_ban_state";
|
|
171
|
+
function saveBanState(prefix, state) {
|
|
172
|
+
setLS(prefixKey(prefix, BAN_STATE_BASE), btoa(JSON.stringify(state)));
|
|
173
|
+
}
|
|
174
|
+
function loadBanState(prefix) {
|
|
175
|
+
const raw = getLS(prefixKey(prefix, BAN_STATE_BASE));
|
|
176
|
+
if (!raw) return null;
|
|
177
|
+
try {
|
|
178
|
+
return JSON.parse(atob(raw));
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function clearBanState(prefix) {
|
|
184
|
+
removeLS(prefixKey(prefix, BAN_STATE_BASE));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/trapKeys.ts
|
|
188
|
+
var TRAP_KEYS = [
|
|
189
|
+
{ base: "_app_cache", valueTemplate: "cache", critical: true },
|
|
190
|
+
{ base: "_usr_pref", valueTemplate: "pref", critical: true },
|
|
191
|
+
{ base: "_sess_meta", valueTemplate: "meta", critical: true },
|
|
192
|
+
{ base: "_ui_theme", valueTemplate: "theme", critical: true },
|
|
193
|
+
{ base: "_ga_tid", valueTemplate: "tid", critical: true }
|
|
194
|
+
];
|
|
195
|
+
var DECOY_KEYS = [
|
|
196
|
+
{ base: "_last_sync", valueTemplate: "iso_datetime", refresh: true },
|
|
197
|
+
{ base: "_font_ver", valueTemplate: "semver", refresh: true },
|
|
198
|
+
{ base: "_notif_read", valueTemplate: "boolean", refresh: false }
|
|
199
|
+
];
|
|
200
|
+
function fullKey(prefix, base) {
|
|
201
|
+
return prefixKey(prefix, base);
|
|
202
|
+
}
|
|
203
|
+
function generateTrapValue(prefix, base, secret) {
|
|
204
|
+
const key = fullKey(prefix, base);
|
|
205
|
+
const hash = generateTrapHash(key, secret);
|
|
206
|
+
return disguiseValue(base, hash);
|
|
207
|
+
}
|
|
208
|
+
function generateDecoyValue(template) {
|
|
209
|
+
switch (template) {
|
|
210
|
+
case "iso_datetime":
|
|
211
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
212
|
+
case "semver":
|
|
213
|
+
return `v${randomMinorVersion()}`;
|
|
214
|
+
case "boolean":
|
|
215
|
+
return "true";
|
|
216
|
+
default:
|
|
217
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function installTrapKeys(prefix, secret) {
|
|
221
|
+
for (const trap of TRAP_KEYS) {
|
|
222
|
+
setLS(fullKey(prefix, trap.base), generateTrapValue(prefix, trap.base, secret));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function installDecoyKeys(prefix) {
|
|
226
|
+
for (const decoy of DECOY_KEYS) {
|
|
227
|
+
setLS(fullKey(prefix, decoy.base), generateDecoyValue(decoy.valueTemplate));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function refreshDecoyKeys(prefix) {
|
|
231
|
+
for (const decoy of DECOY_KEYS) {
|
|
232
|
+
if (decoy.refresh) {
|
|
233
|
+
setLS(fullKey(prefix, decoy.base), generateDecoyValue(decoy.valueTemplate));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function validateTrapKeys(prefix, secret) {
|
|
238
|
+
let missingCritical = false;
|
|
239
|
+
let tampered = false;
|
|
240
|
+
for (const trap of TRAP_KEYS) {
|
|
241
|
+
const key = fullKey(prefix, trap.base);
|
|
242
|
+
const stored = getLS(key);
|
|
243
|
+
if (stored === null) {
|
|
244
|
+
if (trap.critical) missingCritical = true;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const expected = generateTrapValue(prefix, trap.base, secret);
|
|
248
|
+
if (stored !== expected) tampered = true;
|
|
249
|
+
}
|
|
250
|
+
return { valid: !missingCritical && !tampered, missingCritical, tampered };
|
|
251
|
+
}
|
|
252
|
+
function hasTrapKeys(prefix) {
|
|
253
|
+
return TRAP_KEYS.some((t) => getLS(fullKey(prefix, t.base)) !== null);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/ban.ts
|
|
257
|
+
var storedPrefix = null;
|
|
258
|
+
function setStoredPrefix(prefix) {
|
|
259
|
+
storedPrefix = prefix;
|
|
260
|
+
}
|
|
261
|
+
function triggerBan(prefix, secret, level, redirectBan, duration = DEFAULT_DURATION) {
|
|
262
|
+
if (!isBrowser()) return;
|
|
263
|
+
const now = Date.now();
|
|
264
|
+
const currentState = loadBanState(prefix);
|
|
265
|
+
if (currentState && currentState.banLevel === "hard" && level === "soft") {
|
|
266
|
+
if (now < currentState.expiresAt) {
|
|
267
|
+
window.location.href = redirectBan;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const bannedFromPath = window.location.pathname;
|
|
272
|
+
saveBanState(prefix, {
|
|
273
|
+
bannedAt: now,
|
|
274
|
+
expiresAt: now + duration[level],
|
|
275
|
+
bannedFromPath,
|
|
276
|
+
banLevel: level,
|
|
277
|
+
secretHash: generateSecretHash(secret)
|
|
278
|
+
});
|
|
279
|
+
window.location.href = redirectBan;
|
|
280
|
+
}
|
|
281
|
+
function checkBanned(prefix) {
|
|
282
|
+
if (!isBrowser()) return false;
|
|
283
|
+
const state = loadBanState(prefix);
|
|
284
|
+
if (!state) return false;
|
|
285
|
+
if (Date.now() >= state.expiresAt) {
|
|
286
|
+
clearBanState(prefix);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/detection.ts
|
|
293
|
+
var detectionTimer = null;
|
|
294
|
+
function handleKeydown(e, onBan) {
|
|
295
|
+
if (e.key === "F12" || e.keyCode === 123) {
|
|
296
|
+
e.preventDefault();
|
|
297
|
+
onBan();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const ctrlOrMeta = e.ctrlKey || e.metaKey;
|
|
301
|
+
if (ctrlOrMeta && e.shiftKey && ["I", "i", "J", "j", "C", "c"].includes(e.key)) {
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
onBan();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (ctrlOrMeta && (e.key === "u" || e.key === "U") && !e.shiftKey) {
|
|
307
|
+
e.preventDefault();
|
|
308
|
+
onBan();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function handleContextMenu(e, onBan) {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
onBan();
|
|
315
|
+
}
|
|
316
|
+
function checkWindowSizeDiff() {
|
|
317
|
+
const widthDiff = window.outerWidth - window.innerWidth;
|
|
318
|
+
const heightDiff = window.outerHeight - window.innerHeight;
|
|
319
|
+
return widthDiff > WINDOW_SIZE_THRESHOLD || heightDiff > WINDOW_SIZE_THRESHOLD;
|
|
320
|
+
}
|
|
321
|
+
function checkDebuggerTiming() {
|
|
322
|
+
const start = performance.now();
|
|
323
|
+
debugger;
|
|
324
|
+
const end = performance.now();
|
|
325
|
+
return end - start > DEBUGGER_THRESHOLD;
|
|
326
|
+
}
|
|
327
|
+
function checkElementIdTrap() {
|
|
328
|
+
let detected = false;
|
|
329
|
+
const el = document.createElement("div");
|
|
330
|
+
Object.defineProperty(el, "id", {
|
|
331
|
+
get: () => {
|
|
332
|
+
detected = true;
|
|
333
|
+
return "";
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
console.debug(el);
|
|
337
|
+
return detected;
|
|
338
|
+
}
|
|
339
|
+
function checkConsoleLogTrap() {
|
|
340
|
+
let detected = false;
|
|
341
|
+
const obj = {
|
|
342
|
+
toString: () => {
|
|
343
|
+
detected = true;
|
|
344
|
+
return "";
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
console.debug(obj);
|
|
348
|
+
return detected;
|
|
349
|
+
}
|
|
350
|
+
function runDetectionCycle(onBan) {
|
|
351
|
+
if (checkWindowSizeDiff()) {
|
|
352
|
+
onBan();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (checkDebuggerTiming()) {
|
|
356
|
+
onBan();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (checkElementIdTrap()) {
|
|
360
|
+
onBan();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (checkConsoleLogTrap()) {
|
|
364
|
+
onBan();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function startDetection(onSoftBan, shouldEnforce = () => true) {
|
|
369
|
+
const keyHandler = (e) => {
|
|
370
|
+
if (!shouldEnforce()) return;
|
|
371
|
+
handleKeydown(e, onSoftBan);
|
|
372
|
+
};
|
|
373
|
+
const ctxHandler = (e) => {
|
|
374
|
+
if (!shouldEnforce()) return;
|
|
375
|
+
handleContextMenu(e, onSoftBan);
|
|
376
|
+
};
|
|
377
|
+
document.addEventListener("keydown", keyHandler, true);
|
|
378
|
+
document.addEventListener("contextmenu", ctxHandler, true);
|
|
379
|
+
detectionTimer = setInterval(() => {
|
|
380
|
+
if (!shouldEnforce()) return;
|
|
381
|
+
runDetectionCycle(onSoftBan);
|
|
382
|
+
}, DETECTION_INTERVAL);
|
|
383
|
+
return () => {
|
|
384
|
+
document.removeEventListener("keydown", keyHandler, true);
|
|
385
|
+
document.removeEventListener("contextmenu", ctxHandler, true);
|
|
386
|
+
if (detectionTimer) {
|
|
387
|
+
clearInterval(detectionTimer);
|
|
388
|
+
detectionTimer = null;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/pathMatch.ts
|
|
394
|
+
var MATCH_ALL_TOKENS = ["all", "*"];
|
|
395
|
+
function escapeRegex(literal) {
|
|
396
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
397
|
+
}
|
|
398
|
+
function normalizePath(path) {
|
|
399
|
+
let p = path;
|
|
400
|
+
const q = p.indexOf("?");
|
|
401
|
+
if (q !== -1) p = p.slice(0, q);
|
|
402
|
+
const h = p.indexOf("#");
|
|
403
|
+
if (h !== -1) p = p.slice(0, h);
|
|
404
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
405
|
+
return p || "/";
|
|
406
|
+
}
|
|
407
|
+
function patternToRegex(pattern) {
|
|
408
|
+
const segments = normalizePath(pattern).split("/").filter((s) => s !== "");
|
|
409
|
+
let source = "^";
|
|
410
|
+
if (segments.length === 0) {
|
|
411
|
+
source += "/?";
|
|
412
|
+
} else {
|
|
413
|
+
for (const seg of segments) {
|
|
414
|
+
if (/^\[\[\.\.\..+\]\]$/.test(seg)) {
|
|
415
|
+
source += "(?:/[^/]+)*";
|
|
416
|
+
} else if (/^\[\.\.\..+\]$/.test(seg)) {
|
|
417
|
+
source += "(?:/[^/]+)+";
|
|
418
|
+
} else if (/^\[.+\]$/.test(seg)) {
|
|
419
|
+
source += "/[^/]+";
|
|
420
|
+
} else {
|
|
421
|
+
source += "/" + escapeRegex(seg);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
source += "/?$";
|
|
426
|
+
return new RegExp(source);
|
|
427
|
+
}
|
|
428
|
+
function compileProtectPaths(protectPaths) {
|
|
429
|
+
if (protectPaths === void 0 || protectPaths === "all") {
|
|
430
|
+
return () => true;
|
|
431
|
+
}
|
|
432
|
+
if (!Array.isArray(protectPaths) || protectPaths.length === 0) {
|
|
433
|
+
return () => true;
|
|
434
|
+
}
|
|
435
|
+
if (protectPaths.some((p) => MATCH_ALL_TOKENS.includes(String(p).toLowerCase()))) {
|
|
436
|
+
return () => true;
|
|
437
|
+
}
|
|
438
|
+
const regexes = protectPaths.map((p) => patternToRegex(p));
|
|
439
|
+
return (pathname) => {
|
|
440
|
+
const path = normalizePath(pathname);
|
|
441
|
+
return regexes.some((re) => re.test(path));
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/core.ts
|
|
446
|
+
var validationTimer = null;
|
|
447
|
+
var decoyTimer = null;
|
|
448
|
+
var cleanupDetection = null;
|
|
449
|
+
function showConsoleWarning(config) {
|
|
450
|
+
if (!config.consoleWarning) return;
|
|
451
|
+
const { title, description } = config.consoleWarning;
|
|
452
|
+
console.log(`%c${title.text}`, styleObjectToCss(title.style));
|
|
453
|
+
if (description) {
|
|
454
|
+
console.log(`%c${description.text}`, styleObjectToCss(description.style));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
var SUPPRESS_METHODS = ["log", "info", "debug"];
|
|
458
|
+
function activateLogsGuard() {
|
|
459
|
+
for (const method of SUPPRESS_METHODS) {
|
|
460
|
+
const original = console[method];
|
|
461
|
+
console[method] = function(..._args) {
|
|
462
|
+
void original;
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function makeEnforceGate(config) {
|
|
467
|
+
const { redirectBan, excludePaths = [], protectPaths } = config;
|
|
468
|
+
const isProtected = compileProtectPaths(protectPaths);
|
|
469
|
+
return () => {
|
|
470
|
+
const path = window.location.pathname;
|
|
471
|
+
if (path === redirectBan) return false;
|
|
472
|
+
if (excludePaths.some((p) => path.startsWith(p))) return false;
|
|
473
|
+
return isProtected(path);
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function evaluateTrapState(prefix, secret) {
|
|
477
|
+
if (hasTrapKeys(prefix)) {
|
|
478
|
+
const result = validateTrapKeys(prefix, secret);
|
|
479
|
+
if (!result.valid) return "ban";
|
|
480
|
+
if (!hasLocalFingerprint(prefix)) {
|
|
481
|
+
setFingerprint(prefix, generateSecretHash(secret));
|
|
482
|
+
}
|
|
483
|
+
return "ok";
|
|
484
|
+
}
|
|
485
|
+
if (hasLocalFingerprint(prefix)) {
|
|
486
|
+
return "ban";
|
|
487
|
+
}
|
|
488
|
+
installTrapKeys(prefix, secret);
|
|
489
|
+
installDecoyKeys(prefix);
|
|
490
|
+
setFingerprint(prefix, generateSecretHash(secret));
|
|
491
|
+
return "ok";
|
|
492
|
+
}
|
|
493
|
+
function startTrapValidation(prefix, secret, redirectBan, config, shouldEnforce) {
|
|
494
|
+
const duration = config.duration || DEFAULT_DURATION;
|
|
495
|
+
if (validationTimer) clearInterval(validationTimer);
|
|
496
|
+
if (decoyTimer) clearInterval(decoyTimer);
|
|
497
|
+
validationTimer = setInterval(() => {
|
|
498
|
+
if (!shouldEnforce()) return;
|
|
499
|
+
if (checkBanned(prefix)) {
|
|
500
|
+
window.location.href = redirectBan;
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (evaluateTrapState(prefix, secret) === "ban") {
|
|
504
|
+
triggerBan(prefix, secret, "hard", redirectBan, duration);
|
|
505
|
+
}
|
|
506
|
+
}, VALIDATION_INTERVAL);
|
|
507
|
+
decoyTimer = setInterval(() => {
|
|
508
|
+
refreshDecoyKeys(prefix);
|
|
509
|
+
}, DECOY_REFRESH_INTERVAL);
|
|
510
|
+
}
|
|
511
|
+
function boot(config) {
|
|
512
|
+
const {
|
|
513
|
+
prefix,
|
|
514
|
+
currentEnv,
|
|
515
|
+
excludeEnvs,
|
|
516
|
+
redirectBan,
|
|
517
|
+
hashSecret,
|
|
518
|
+
duration = DEFAULT_DURATION
|
|
519
|
+
} = config;
|
|
520
|
+
setStoredPrefix(prefix);
|
|
521
|
+
if (excludeEnvs.includes(currentEnv)) return;
|
|
522
|
+
const currentPath = window.location.pathname;
|
|
523
|
+
const shouldEnforce = makeEnforceGate(config);
|
|
524
|
+
const enforceEntry = shouldEnforce();
|
|
525
|
+
const onBanPage = currentPath === redirectBan;
|
|
526
|
+
if (enforceEntry || onBanPage) {
|
|
527
|
+
showConsoleWarning(config);
|
|
528
|
+
activateLogsGuard();
|
|
529
|
+
}
|
|
530
|
+
if (onBanPage) return;
|
|
531
|
+
if (enforceEntry && checkBanned(prefix)) {
|
|
532
|
+
window.location.href = redirectBan;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (evaluateTrapState(prefix, hashSecret) === "ban" && enforceEntry) {
|
|
536
|
+
triggerBan(prefix, hashSecret, "hard", redirectBan, duration);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const disableOnTouch = config.disableOnTouchDevice !== false;
|
|
540
|
+
if (!(disableOnTouch && isTouchDevice())) {
|
|
541
|
+
cleanupDetection = startDetection(() => {
|
|
542
|
+
triggerBan(prefix, hashSecret, "soft", redirectBan, duration);
|
|
543
|
+
}, shouldEnforce);
|
|
544
|
+
}
|
|
545
|
+
startTrapValidation(prefix, hashSecret, redirectBan, config, shouldEnforce);
|
|
546
|
+
}
|
|
547
|
+
function initDevtoolsGuard(config) {
|
|
548
|
+
if (!isBrowser()) return;
|
|
549
|
+
setStoredPrefix(config.prefix);
|
|
550
|
+
if (document.readyState === "loading") {
|
|
551
|
+
document.addEventListener("DOMContentLoaded", () => boot(config));
|
|
552
|
+
} else {
|
|
553
|
+
boot(config);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/next.ts
|
|
558
|
+
function DevtoolsGuardScript(props) {
|
|
559
|
+
const initialized = (0, import_react.useRef)(false);
|
|
560
|
+
(0, import_react.useEffect)(() => {
|
|
561
|
+
if (initialized.current) return;
|
|
562
|
+
initialized.current = true;
|
|
563
|
+
initDevtoolsGuard(props);
|
|
564
|
+
}, []);
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
568
|
+
0 && (module.exports = {
|
|
569
|
+
DevtoolsGuardScript
|
|
570
|
+
});
|
package/dist/next.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
initDevtoolsGuard
|
|
4
|
+
} from "./chunk-OKFPKHFA.mjs";
|
|
5
|
+
|
|
6
|
+
// src/next.ts
|
|
7
|
+
import { useEffect, useRef } from "react";
|
|
8
|
+
function DevtoolsGuardScript(props) {
|
|
9
|
+
const initialized = useRef(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (initialized.current) return;
|
|
12
|
+
initialized.current = true;
|
|
13
|
+
initDevtoolsGuard(props);
|
|
14
|
+
}, []);
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
DevtoolsGuardScript
|
|
19
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
interface ConsoleWarningTextConfig {
|
|
2
|
+
text: string;
|
|
3
|
+
style: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
interface ConsoleWarningConfig {
|
|
6
|
+
title: ConsoleWarningTextConfig;
|
|
7
|
+
description?: ConsoleWarningTextConfig;
|
|
8
|
+
}
|
|
9
|
+
interface DurationConfig {
|
|
10
|
+
soft: number;
|
|
11
|
+
hard: number;
|
|
12
|
+
}
|
|
13
|
+
type ProtectPaths = string[] | 'all';
|
|
14
|
+
interface DevtoolsGuardConfig {
|
|
15
|
+
prefix: string;
|
|
16
|
+
currentEnv: string;
|
|
17
|
+
excludeEnvs: string[];
|
|
18
|
+
redirectBan: string;
|
|
19
|
+
hashSecret: string;
|
|
20
|
+
duration?: DurationConfig;
|
|
21
|
+
autoRefreshOnExpire?: boolean;
|
|
22
|
+
excludePaths?: string[];
|
|
23
|
+
protectPaths?: ProtectPaths;
|
|
24
|
+
disableOnTouchDevice?: boolean;
|
|
25
|
+
consoleWarning?: ConsoleWarningConfig;
|
|
26
|
+
}
|
|
27
|
+
type BanLevel = 'soft' | 'hard';
|
|
28
|
+
interface BanStatus {
|
|
29
|
+
isBanned: boolean;
|
|
30
|
+
bannedAt: number | null;
|
|
31
|
+
expiresAt: number | null;
|
|
32
|
+
bannedFromPath: string | null;
|
|
33
|
+
banLevel: BanLevel | null;
|
|
34
|
+
}
|
|
35
|
+
interface UnlockResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
result: {
|
|
38
|
+
path: string;
|
|
39
|
+
message: string;
|
|
40
|
+
} | null;
|
|
41
|
+
message?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type { BanStatus as B, ConsoleWarningConfig as C, DevtoolsGuardConfig as D, ProtectPaths as P, UnlockResult as U, BanLevel as a, ConsoleWarningTextConfig as b, DurationConfig as c };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
interface ConsoleWarningTextConfig {
|
|
2
|
+
text: string;
|
|
3
|
+
style: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
interface ConsoleWarningConfig {
|
|
6
|
+
title: ConsoleWarningTextConfig;
|
|
7
|
+
description?: ConsoleWarningTextConfig;
|
|
8
|
+
}
|
|
9
|
+
interface DurationConfig {
|
|
10
|
+
soft: number;
|
|
11
|
+
hard: number;
|
|
12
|
+
}
|
|
13
|
+
type ProtectPaths = string[] | 'all';
|
|
14
|
+
interface DevtoolsGuardConfig {
|
|
15
|
+
prefix: string;
|
|
16
|
+
currentEnv: string;
|
|
17
|
+
excludeEnvs: string[];
|
|
18
|
+
redirectBan: string;
|
|
19
|
+
hashSecret: string;
|
|
20
|
+
duration?: DurationConfig;
|
|
21
|
+
autoRefreshOnExpire?: boolean;
|
|
22
|
+
excludePaths?: string[];
|
|
23
|
+
protectPaths?: ProtectPaths;
|
|
24
|
+
disableOnTouchDevice?: boolean;
|
|
25
|
+
consoleWarning?: ConsoleWarningConfig;
|
|
26
|
+
}
|
|
27
|
+
type BanLevel = 'soft' | 'hard';
|
|
28
|
+
interface BanStatus {
|
|
29
|
+
isBanned: boolean;
|
|
30
|
+
bannedAt: number | null;
|
|
31
|
+
expiresAt: number | null;
|
|
32
|
+
bannedFromPath: string | null;
|
|
33
|
+
banLevel: BanLevel | null;
|
|
34
|
+
}
|
|
35
|
+
interface UnlockResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
result: {
|
|
38
|
+
path: string;
|
|
39
|
+
message: string;
|
|
40
|
+
} | null;
|
|
41
|
+
message?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type { BanStatus as B, ConsoleWarningConfig as C, DevtoolsGuardConfig as D, ProtectPaths as P, UnlockResult as U, BanLevel as a, ConsoleWarningTextConfig as b, DurationConfig as c };
|