@rcnr/lockdown 1.1.1 → 1.2.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/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +100 -1
- package/dist/index.mjs +100 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ interface Violation {
|
|
|
4
4
|
timestamp: number;
|
|
5
5
|
}
|
|
6
6
|
/** All violation types the lockdown hook can detect. */
|
|
7
|
-
type ViolationType = "fullscreen_exit" | "tab_switch" | "window_blur" | "paste_attempt" | "copy_attempt" | "cut_attempt" | "drop_attempt" | "devtools_attempt";
|
|
7
|
+
type ViolationType = "fullscreen_exit" | "tab_switch" | "window_blur" | "paste_attempt" | "copy_attempt" | "cut_attempt" | "drop_attempt" | "devtools_attempt" | "extension_detected";
|
|
8
8
|
/** Violations that trigger instant auto-submit (cheating attempts). */
|
|
9
9
|
declare const INSTANT_SUBMIT_VIOLATIONS: Set<ViolationType>;
|
|
10
10
|
/** Configuration for the lockdown hook. */
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ interface Violation {
|
|
|
4
4
|
timestamp: number;
|
|
5
5
|
}
|
|
6
6
|
/** All violation types the lockdown hook can detect. */
|
|
7
|
-
type ViolationType = "fullscreen_exit" | "tab_switch" | "window_blur" | "paste_attempt" | "copy_attempt" | "cut_attempt" | "drop_attempt" | "devtools_attempt";
|
|
7
|
+
type ViolationType = "fullscreen_exit" | "tab_switch" | "window_blur" | "paste_attempt" | "copy_attempt" | "cut_attempt" | "drop_attempt" | "devtools_attempt" | "extension_detected";
|
|
8
8
|
/** Violations that trigger instant auto-submit (cheating attempts). */
|
|
9
9
|
declare const INSTANT_SUBMIT_VIOLATIONS: Set<ViolationType>;
|
|
10
10
|
/** Configuration for the lockdown hook. */
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,8 @@ var INSTANT_SUBMIT_VIOLATIONS = /* @__PURE__ */ new Set([
|
|
|
34
34
|
"copy_attempt",
|
|
35
35
|
"cut_attempt",
|
|
36
36
|
"drop_attempt",
|
|
37
|
-
"devtools_attempt"
|
|
37
|
+
"devtools_attempt",
|
|
38
|
+
"extension_detected"
|
|
38
39
|
]);
|
|
39
40
|
|
|
40
41
|
// src/useLockdown.ts
|
|
@@ -313,6 +314,104 @@ function useLockdown({
|
|
|
313
314
|
document.removeEventListener("contextmenu", handleContextMenu);
|
|
314
315
|
};
|
|
315
316
|
}, [enabled, addViolation, startCountdown, clearCountdown]);
|
|
317
|
+
(0, import_react.useEffect)(() => {
|
|
318
|
+
if (!enabled) return;
|
|
319
|
+
const EXTENSION_SIGNATURES = [
|
|
320
|
+
"grammarly",
|
|
321
|
+
"schoolai",
|
|
322
|
+
"quillbot",
|
|
323
|
+
"languagetool",
|
|
324
|
+
"ginger",
|
|
325
|
+
"writefull",
|
|
326
|
+
"wordtune",
|
|
327
|
+
"prowritingaid",
|
|
328
|
+
"hemingway",
|
|
329
|
+
"copyleaks",
|
|
330
|
+
"jenni",
|
|
331
|
+
"jasper",
|
|
332
|
+
"textblaze",
|
|
333
|
+
"compose-ai",
|
|
334
|
+
"hyperwrite",
|
|
335
|
+
"otter-ai",
|
|
336
|
+
"fireflies"
|
|
337
|
+
];
|
|
338
|
+
function isExtensionElement(el) {
|
|
339
|
+
if (el.tagName === "IFRAME") {
|
|
340
|
+
const src = el.getAttribute("src") || "";
|
|
341
|
+
if (/^(chrome|moz)-extension:\/\//i.test(src)) return true;
|
|
342
|
+
if (!src || src.startsWith("blob:")) {
|
|
343
|
+
const id2 = el.id?.toLowerCase() || "";
|
|
344
|
+
const cls2 = typeof el.className === "string" ? el.className.toLowerCase() : "";
|
|
345
|
+
if (EXTENSION_SIGNATURES.some(
|
|
346
|
+
(sig) => id2.includes(sig) || cls2.includes(sig)
|
|
347
|
+
)) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (el.shadowRoot && !el.hasAttribute("data-rcnr")) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
const id = el.id?.toLowerCase() || "";
|
|
356
|
+
const cls = typeof el.className === "string" ? el.className.toLowerCase() : "";
|
|
357
|
+
for (const attr of el.getAttributeNames?.() || []) {
|
|
358
|
+
const attrLower = attr.toLowerCase();
|
|
359
|
+
if (EXTENSION_SIGNATURES.some((sig) => attrLower.includes(sig))) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (EXTENSION_SIGNATURES.some(
|
|
364
|
+
(sig) => id.includes(sig) || cls.includes(sig)
|
|
365
|
+
)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
if (el.parentElement === document.body && !el.hasAttribute("data-rcnr")) {
|
|
369
|
+
const tag = el.tagName?.toLowerCase() || "";
|
|
370
|
+
if (tag === "div" || tag === "section" || tag === "aside") {
|
|
371
|
+
const style = window.getComputedStyle(el);
|
|
372
|
+
if ((style.position === "fixed" || style.position === "absolute") && parseInt(style.width, 10) > 200) {
|
|
373
|
+
const html = el.innerHTML?.toLowerCase() || "";
|
|
374
|
+
if (/chrome-extension:|moz-extension:/i.test(html)) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
const observer = new MutationObserver((mutations) => {
|
|
383
|
+
if (!hasEnteredFullscreenRef.current) return;
|
|
384
|
+
if (graceRef.current) return;
|
|
385
|
+
if (autoSubmittedRef.current) return;
|
|
386
|
+
for (const mutation of mutations) {
|
|
387
|
+
for (const node of mutation.addedNodes) {
|
|
388
|
+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
389
|
+
if (isExtensionElement(node)) {
|
|
390
|
+
addViolation("extension_detected");
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
observer.observe(document.documentElement, {
|
|
397
|
+
childList: true,
|
|
398
|
+
subtree: true
|
|
399
|
+
});
|
|
400
|
+
const scanTimer = requestAnimationFrame(() => {
|
|
401
|
+
if (!hasEnteredFullscreenRef.current) return;
|
|
402
|
+
const allElements = document.querySelectorAll("*");
|
|
403
|
+
for (const el of allElements) {
|
|
404
|
+
if (isExtensionElement(el)) {
|
|
405
|
+
addViolation("extension_detected");
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
return () => {
|
|
411
|
+
observer.disconnect();
|
|
412
|
+
cancelAnimationFrame(scanTimer);
|
|
413
|
+
};
|
|
414
|
+
}, [enabled, addViolation]);
|
|
316
415
|
(0, import_react.useEffect)(() => {
|
|
317
416
|
if (!enabled) return;
|
|
318
417
|
const interval = setInterval(() => {
|
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,8 @@ var INSTANT_SUBMIT_VIOLATIONS = /* @__PURE__ */ new Set([
|
|
|
7
7
|
"copy_attempt",
|
|
8
8
|
"cut_attempt",
|
|
9
9
|
"drop_attempt",
|
|
10
|
-
"devtools_attempt"
|
|
10
|
+
"devtools_attempt",
|
|
11
|
+
"extension_detected"
|
|
11
12
|
]);
|
|
12
13
|
|
|
13
14
|
// src/useLockdown.ts
|
|
@@ -286,6 +287,104 @@ function useLockdown({
|
|
|
286
287
|
document.removeEventListener("contextmenu", handleContextMenu);
|
|
287
288
|
};
|
|
288
289
|
}, [enabled, addViolation, startCountdown, clearCountdown]);
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
if (!enabled) return;
|
|
292
|
+
const EXTENSION_SIGNATURES = [
|
|
293
|
+
"grammarly",
|
|
294
|
+
"schoolai",
|
|
295
|
+
"quillbot",
|
|
296
|
+
"languagetool",
|
|
297
|
+
"ginger",
|
|
298
|
+
"writefull",
|
|
299
|
+
"wordtune",
|
|
300
|
+
"prowritingaid",
|
|
301
|
+
"hemingway",
|
|
302
|
+
"copyleaks",
|
|
303
|
+
"jenni",
|
|
304
|
+
"jasper",
|
|
305
|
+
"textblaze",
|
|
306
|
+
"compose-ai",
|
|
307
|
+
"hyperwrite",
|
|
308
|
+
"otter-ai",
|
|
309
|
+
"fireflies"
|
|
310
|
+
];
|
|
311
|
+
function isExtensionElement(el) {
|
|
312
|
+
if (el.tagName === "IFRAME") {
|
|
313
|
+
const src = el.getAttribute("src") || "";
|
|
314
|
+
if (/^(chrome|moz)-extension:\/\//i.test(src)) return true;
|
|
315
|
+
if (!src || src.startsWith("blob:")) {
|
|
316
|
+
const id2 = el.id?.toLowerCase() || "";
|
|
317
|
+
const cls2 = typeof el.className === "string" ? el.className.toLowerCase() : "";
|
|
318
|
+
if (EXTENSION_SIGNATURES.some(
|
|
319
|
+
(sig) => id2.includes(sig) || cls2.includes(sig)
|
|
320
|
+
)) {
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (el.shadowRoot && !el.hasAttribute("data-rcnr")) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
const id = el.id?.toLowerCase() || "";
|
|
329
|
+
const cls = typeof el.className === "string" ? el.className.toLowerCase() : "";
|
|
330
|
+
for (const attr of el.getAttributeNames?.() || []) {
|
|
331
|
+
const attrLower = attr.toLowerCase();
|
|
332
|
+
if (EXTENSION_SIGNATURES.some((sig) => attrLower.includes(sig))) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (EXTENSION_SIGNATURES.some(
|
|
337
|
+
(sig) => id.includes(sig) || cls.includes(sig)
|
|
338
|
+
)) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
if (el.parentElement === document.body && !el.hasAttribute("data-rcnr")) {
|
|
342
|
+
const tag = el.tagName?.toLowerCase() || "";
|
|
343
|
+
if (tag === "div" || tag === "section" || tag === "aside") {
|
|
344
|
+
const style = window.getComputedStyle(el);
|
|
345
|
+
if ((style.position === "fixed" || style.position === "absolute") && parseInt(style.width, 10) > 200) {
|
|
346
|
+
const html = el.innerHTML?.toLowerCase() || "";
|
|
347
|
+
if (/chrome-extension:|moz-extension:/i.test(html)) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
const observer = new MutationObserver((mutations) => {
|
|
356
|
+
if (!hasEnteredFullscreenRef.current) return;
|
|
357
|
+
if (graceRef.current) return;
|
|
358
|
+
if (autoSubmittedRef.current) return;
|
|
359
|
+
for (const mutation of mutations) {
|
|
360
|
+
for (const node of mutation.addedNodes) {
|
|
361
|
+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
362
|
+
if (isExtensionElement(node)) {
|
|
363
|
+
addViolation("extension_detected");
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
observer.observe(document.documentElement, {
|
|
370
|
+
childList: true,
|
|
371
|
+
subtree: true
|
|
372
|
+
});
|
|
373
|
+
const scanTimer = requestAnimationFrame(() => {
|
|
374
|
+
if (!hasEnteredFullscreenRef.current) return;
|
|
375
|
+
const allElements = document.querySelectorAll("*");
|
|
376
|
+
for (const el of allElements) {
|
|
377
|
+
if (isExtensionElement(el)) {
|
|
378
|
+
addViolation("extension_detected");
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
return () => {
|
|
384
|
+
observer.disconnect();
|
|
385
|
+
cancelAnimationFrame(scanTimer);
|
|
386
|
+
};
|
|
387
|
+
}, [enabled, addViolation]);
|
|
289
388
|
useEffect(() => {
|
|
290
389
|
if (!enabled) return;
|
|
291
390
|
const interval = setInterval(() => {
|