console-self-xss-warning 1.0.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/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/detectLanguage.d.ts +1 -0
- package/dist/detectLanguage.js +13 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +192 -0
- package/dist/translations.default.json +126 -0
- package/package.json +40 -0
- package/src/detectLanguage.ts +17 -0
- package/src/index.ts +293 -0
- package/src/translations.default.json +126 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hksm-app
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# console-self-xss-warning
|
|
2
|
+
|
|
3
|
+
Show a self-XSS warning in the browser console to protect users from social
|
|
4
|
+
engineering.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
npm install console-self-xss-warning
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { showConsoleWarning } from "console-self-xss-warning";
|
|
16
|
+
|
|
17
|
+
showConsoleWarning();
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Next.js (App Router)
|
|
21
|
+
|
|
22
|
+
Call it in a client component so it runs only in the browser.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
"use client";
|
|
26
|
+
|
|
27
|
+
import { useEffect } from "react";
|
|
28
|
+
import { showConsoleWarning } from "console-self-xss-warning";
|
|
29
|
+
|
|
30
|
+
export function ConsoleWarning() {
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
showConsoleWarning({ productionOnly: true });
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Re-log on route changes:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
"use client";
|
|
43
|
+
|
|
44
|
+
import { useEffect } from "react";
|
|
45
|
+
import { usePathname } from "next/navigation";
|
|
46
|
+
import { showConsoleWarning } from "console-self-xss-warning";
|
|
47
|
+
|
|
48
|
+
export function ConsoleWarning() {
|
|
49
|
+
const pathname = usePathname();
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
showConsoleWarning({ once: false, clearConsole: false, productionOnly: true });
|
|
53
|
+
}, [pathname]);
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
| parameter | default | required | description |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `translations` | built-in translations | no | override texts and styles per language |
|
|
64
|
+
| `forceLang` | auto-detect (`navigator.language`) | no | force a specific language |
|
|
65
|
+
| `once` | `true` | no | show only once per page load |
|
|
66
|
+
| `productionOnly` | `false` | no | show only in production mode |
|
|
67
|
+
| `clearConsole` | `false` | no | clear the console before logging |
|
|
68
|
+
| `productionOnlyEnvKey` | default key list | no | read `productionOnly` from an env variable |
|
|
69
|
+
|
|
70
|
+
### Common options
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
showConsoleWarning({
|
|
74
|
+
once: true, // show only once per page load
|
|
75
|
+
clearConsole: false, // optionally clear console before warning
|
|
76
|
+
productionOnly: false // gate by production environment
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Custom translation
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
showConsoleWarning({
|
|
84
|
+
translations: {
|
|
85
|
+
en: {
|
|
86
|
+
title: "STOP!",
|
|
87
|
+
message:
|
|
88
|
+
"This is for developers only.\nIf someone asks you to paste code here, it is a scam.",
|
|
89
|
+
titleStyle: "color:#d00;font-size:52px;font-weight:900;",
|
|
90
|
+
messageStyle: "font-size:16px;"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Full custom JSON
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const translations = {
|
|
100
|
+
en: {
|
|
101
|
+
title: "STOP!",
|
|
102
|
+
message: "Private area.\nDo not paste anything here."
|
|
103
|
+
},
|
|
104
|
+
fr: {
|
|
105
|
+
title: "STOP !",
|
|
106
|
+
message: "Zone privée.\nNe collez rien ici."
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
showConsoleWarning({
|
|
111
|
+
translations,
|
|
112
|
+
forceLang: "en",
|
|
113
|
+
clearConsole: true
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Environment config for productionOnly
|
|
118
|
+
|
|
119
|
+
If you want `productionOnly` to be driven by an environment variable, pass
|
|
120
|
+
your own key name using `productionOnlyEnvKey`.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
showConsoleWarning({
|
|
124
|
+
productionOnlyEnvKey: "MY_APP_CONSOLE_WARNING_PROD_ONLY"
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
You can also pass multiple keys (first match wins):
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
showConsoleWarning({
|
|
132
|
+
productionOnlyEnvKey: ["MY_KEY_1", "MY_KEY_2"]
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Boolean values accepted: `1/0`, `true/false`, `yes/no`, `on/off`
|
|
137
|
+
|
|
138
|
+
Default keys (if you do not pass anything):
|
|
139
|
+
|
|
140
|
+
- `CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY`
|
|
141
|
+
- `VITE_CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY`
|
|
142
|
+
- `NEXT_PUBLIC_CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY`
|
|
143
|
+
|
|
144
|
+
## API
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
type Options = {
|
|
148
|
+
translations?: {
|
|
149
|
+
[lang: string]: {
|
|
150
|
+
title: string
|
|
151
|
+
message: string
|
|
152
|
+
titleStyle?: string
|
|
153
|
+
messageStyle?: string
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
forceLang?: string
|
|
157
|
+
once?: boolean
|
|
158
|
+
productionOnly?: boolean
|
|
159
|
+
clearConsole?: boolean
|
|
160
|
+
productionOnlyEnvKey?: string | string[]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
showConsoleWarning(options?)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Notes
|
|
167
|
+
|
|
168
|
+
- Runs only in the browser and is safe for SSR.
|
|
169
|
+
- Default language is auto-detected via `navigator.language` with `en` fallback.
|
|
170
|
+
- `\\n` in translations is converted to a real line break in console output.
|
|
171
|
+
- No external dependencies.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function detectLanguage(fallback?: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function detectLanguage(fallback = "en") {
|
|
2
|
+
if (typeof navigator === "undefined") {
|
|
3
|
+
return fallback;
|
|
4
|
+
}
|
|
5
|
+
const raw = (Array.isArray(navigator.languages) && navigator.languages[0]) ||
|
|
6
|
+
navigator.language ||
|
|
7
|
+
fallback;
|
|
8
|
+
if (!raw) {
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
const normalized = raw.toLowerCase().replace("_", "-");
|
|
12
|
+
return normalized.split("-")[0] || fallback;
|
|
13
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Translation = {
|
|
2
|
+
title: string;
|
|
3
|
+
message: string;
|
|
4
|
+
titleStyle?: string;
|
|
5
|
+
messageStyle?: string;
|
|
6
|
+
};
|
|
7
|
+
export type Options = {
|
|
8
|
+
translations?: Record<string, Translation>;
|
|
9
|
+
forceLang?: string;
|
|
10
|
+
once?: boolean;
|
|
11
|
+
productionOnly?: boolean;
|
|
12
|
+
clearConsole?: boolean;
|
|
13
|
+
productionOnlyEnvKey?: string | string[];
|
|
14
|
+
};
|
|
15
|
+
export declare function showConsoleWarning(options?: Options): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import defaultTranslations from "./translations.default.json";
|
|
2
|
+
import { detectLanguage } from "./detectLanguage";
|
|
3
|
+
const DEFAULT_TITLE_STYLE = "color:red;font-size:48px;font-weight:bold;";
|
|
4
|
+
const DEFAULT_MESSAGE_STYLE = "font-size:16px;";
|
|
5
|
+
const DEFAULT_SPAM_INTERVAL_MS = 2000;
|
|
6
|
+
const DEVTOOLS_SIZE_THRESHOLD_PX = 160;
|
|
7
|
+
const GLOBAL_ONCE_FLAG = "__consoleSelfXssWarningShown__";
|
|
8
|
+
let hasShown = false;
|
|
9
|
+
let spamIntervalId;
|
|
10
|
+
let spamPayload;
|
|
11
|
+
function getHasShown() {
|
|
12
|
+
if (hasShown) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (typeof globalThis !== "undefined") {
|
|
16
|
+
return Boolean(globalThis[GLOBAL_ONCE_FLAG]);
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function setHasShown() {
|
|
21
|
+
hasShown = true;
|
|
22
|
+
if (typeof globalThis !== "undefined") {
|
|
23
|
+
globalThis[GLOBAL_ONCE_FLAG] = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isDevtoolsLikelyOpen() {
|
|
27
|
+
if (typeof window === "undefined") {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return (window.outerWidth - window.innerWidth > DEVTOOLS_SIZE_THRESHOLD_PX ||
|
|
31
|
+
window.outerHeight - window.innerHeight > DEVTOOLS_SIZE_THRESHOLD_PX);
|
|
32
|
+
}
|
|
33
|
+
function isBrowser() {
|
|
34
|
+
return typeof window !== "undefined" && typeof console !== "undefined";
|
|
35
|
+
}
|
|
36
|
+
function isProduction() {
|
|
37
|
+
const proc = typeof globalThis !== "undefined"
|
|
38
|
+
? globalThis.process
|
|
39
|
+
: undefined;
|
|
40
|
+
const nodeEnv = proc && proc.env && typeof proc.env.NODE_ENV === "string"
|
|
41
|
+
? proc.env.NODE_ENV
|
|
42
|
+
: undefined;
|
|
43
|
+
if (nodeEnv) {
|
|
44
|
+
return nodeEnv === "production";
|
|
45
|
+
}
|
|
46
|
+
const metaEnv = typeof import.meta !== "undefined"
|
|
47
|
+
? import.meta.env
|
|
48
|
+
: undefined;
|
|
49
|
+
if (metaEnv) {
|
|
50
|
+
if (typeof metaEnv.PROD === "boolean") {
|
|
51
|
+
return metaEnv.PROD;
|
|
52
|
+
}
|
|
53
|
+
if (typeof metaEnv.MODE === "string") {
|
|
54
|
+
return metaEnv.MODE === "production";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function parseEnvBoolean(value) {
|
|
60
|
+
if (typeof value === "boolean") {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
if (typeof value !== "string") {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const normalized = value.trim().toLowerCase();
|
|
67
|
+
if (["1", "true", "yes", "y", "on"].includes(normalized)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (["0", "false", "no", "n", "off"].includes(normalized)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const DEFAULT_PRODUCTION_ONLY_ENV_KEYS = [
|
|
76
|
+
"CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY",
|
|
77
|
+
"VITE_CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY",
|
|
78
|
+
"NEXT_PUBLIC_CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY"
|
|
79
|
+
];
|
|
80
|
+
function resolveEnvValue(env, keys) {
|
|
81
|
+
if (!env) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
if (key in env) {
|
|
86
|
+
return env[key];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
function getConfigProductionOnly(keys) {
|
|
92
|
+
const keyList = typeof keys === "string"
|
|
93
|
+
? [keys]
|
|
94
|
+
: Array.isArray(keys) && keys.length > 0
|
|
95
|
+
? keys
|
|
96
|
+
: DEFAULT_PRODUCTION_ONLY_ENV_KEYS;
|
|
97
|
+
const proc = typeof globalThis !== "undefined"
|
|
98
|
+
? globalThis.process
|
|
99
|
+
: undefined;
|
|
100
|
+
const nodeEnvValue = resolveEnvValue(proc && proc.env ? proc.env : undefined, keyList);
|
|
101
|
+
const metaEnv = typeof import.meta !== "undefined"
|
|
102
|
+
? import.meta.env
|
|
103
|
+
: undefined;
|
|
104
|
+
const metaValue = resolveEnvValue(metaEnv, keyList);
|
|
105
|
+
return parseEnvBoolean(nodeEnvValue !== null && nodeEnvValue !== void 0 ? nodeEnvValue : metaValue);
|
|
106
|
+
}
|
|
107
|
+
function mergeTranslations(overrides) {
|
|
108
|
+
if (!overrides) {
|
|
109
|
+
return defaultTranslations;
|
|
110
|
+
}
|
|
111
|
+
const merged = {
|
|
112
|
+
...defaultTranslations,
|
|
113
|
+
...overrides
|
|
114
|
+
};
|
|
115
|
+
return merged;
|
|
116
|
+
}
|
|
117
|
+
function resolveTranslation(translations, lang) {
|
|
118
|
+
const fallback = translations.en ||
|
|
119
|
+
defaultTranslations.en || {
|
|
120
|
+
title: "STOP!",
|
|
121
|
+
message: "This is a browser feature for developers.\nIf someone asks you to paste something here — it is a scam."
|
|
122
|
+
};
|
|
123
|
+
const selected = translations[lang] || translations[lang.toLowerCase()];
|
|
124
|
+
if (!selected) {
|
|
125
|
+
return fallback;
|
|
126
|
+
}
|
|
127
|
+
const rawTitle = selected.title || fallback.title;
|
|
128
|
+
const rawMessage = selected.message || fallback.message;
|
|
129
|
+
return {
|
|
130
|
+
title: rawTitle.replace(/\\n/g, "\n"),
|
|
131
|
+
message: rawMessage.replace(/\\n/g, "\n"),
|
|
132
|
+
titleStyle: selected.titleStyle,
|
|
133
|
+
messageStyle: selected.messageStyle
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function logWarning(payload) {
|
|
137
|
+
if (payload.clearConsole && typeof console.clear === "function") {
|
|
138
|
+
console.clear();
|
|
139
|
+
}
|
|
140
|
+
console.log(`%c${payload.title}`, payload.titleStyle || DEFAULT_TITLE_STYLE);
|
|
141
|
+
console.log(`%c${payload.message}`, payload.messageStyle || DEFAULT_MESSAGE_STYLE);
|
|
142
|
+
}
|
|
143
|
+
function startSpam(payload) {
|
|
144
|
+
spamPayload = payload;
|
|
145
|
+
if (typeof window === "undefined") {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (spamIntervalId !== undefined) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (isDevtoolsLikelyOpen()) {
|
|
152
|
+
logWarning(payload);
|
|
153
|
+
setHasShown();
|
|
154
|
+
}
|
|
155
|
+
spamIntervalId = window.setInterval(() => {
|
|
156
|
+
if (!spamPayload || !isDevtoolsLikelyOpen()) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
logWarning(spamPayload);
|
|
160
|
+
setHasShown();
|
|
161
|
+
}, DEFAULT_SPAM_INTERVAL_MS);
|
|
162
|
+
}
|
|
163
|
+
export function showConsoleWarning(options = {}) {
|
|
164
|
+
var _a;
|
|
165
|
+
if (!isBrowser()) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const { translations: translationOverrides, forceLang, once = true, productionOnly, clearConsole = false, productionOnlyEnvKey } = options;
|
|
169
|
+
const resolvedProductionOnly = (_a = productionOnly !== null && productionOnly !== void 0 ? productionOnly : getConfigProductionOnly(productionOnlyEnvKey)) !== null && _a !== void 0 ? _a : false;
|
|
170
|
+
if (resolvedProductionOnly && !isProduction()) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (once && getHasShown()) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const translations = mergeTranslations(translationOverrides);
|
|
177
|
+
const lang = (forceLang || detectLanguage("en")).toLowerCase();
|
|
178
|
+
const { title, message, titleStyle, messageStyle } = resolveTranslation(translations, lang);
|
|
179
|
+
const payload = {
|
|
180
|
+
title,
|
|
181
|
+
message,
|
|
182
|
+
titleStyle,
|
|
183
|
+
messageStyle,
|
|
184
|
+
clearConsole
|
|
185
|
+
};
|
|
186
|
+
if (!once) {
|
|
187
|
+
startSpam(payload);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
logWarning(payload);
|
|
191
|
+
setHasShown();
|
|
192
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en": {
|
|
3
|
+
"title": "STOP!",
|
|
4
|
+
"message": "This is a browser feature for developers.\\nIf someone asks you to paste something here — it is a scam."
|
|
5
|
+
},
|
|
6
|
+
"es": {
|
|
7
|
+
"title": "¡ALTO!",
|
|
8
|
+
"message": "Esta es una función del navegador para desarrolladores.\\nSi alguien te pide que pegues algo aquí, es una estafa."
|
|
9
|
+
},
|
|
10
|
+
"fr": {
|
|
11
|
+
"title": "STOP !",
|
|
12
|
+
"message": "Ceci est une fonctionnalité du navigateur pour les développeurs.\\nSi quelqu'un vous demande de coller quelque chose ici, c'est une arnaque."
|
|
13
|
+
},
|
|
14
|
+
"de": {
|
|
15
|
+
"title": "STOPP!",
|
|
16
|
+
"message": "Dies ist eine Browserfunktion für Entwickler.\\nWenn dich jemand bittet, hier etwas einzufügen, ist das Betrug."
|
|
17
|
+
},
|
|
18
|
+
"it": {
|
|
19
|
+
"title": "STOP!",
|
|
20
|
+
"message": "Questa è una funzione del browser per sviluppatori.\\nSe qualcuno ti chiede di incollare qualcosa qui, è una truffa."
|
|
21
|
+
},
|
|
22
|
+
"pt": {
|
|
23
|
+
"title": "PARE!",
|
|
24
|
+
"message": "Este é um recurso do navegador para desenvolvedores.\\nSe alguém pedir para você colar algo aqui, é um golpe."
|
|
25
|
+
},
|
|
26
|
+
"nl": {
|
|
27
|
+
"title": "STOP!",
|
|
28
|
+
"message": "Dit is een browserfunctie voor ontwikkelaars.\\nAls iemand je vraagt hier iets te plakken, is dat oplichting."
|
|
29
|
+
},
|
|
30
|
+
"sv": {
|
|
31
|
+
"title": "STOPP!",
|
|
32
|
+
"message": "Det här är en webbläsarfunktion för utvecklare.\\nOm någon ber dig klistra in något här är det en bluff."
|
|
33
|
+
},
|
|
34
|
+
"no": {
|
|
35
|
+
"title": "STOPP!",
|
|
36
|
+
"message": "Dette er en nettleserfunksjon for utviklere.\\nHvis noen ber deg lime inn noe her, er det svindel."
|
|
37
|
+
},
|
|
38
|
+
"da": {
|
|
39
|
+
"title": "STOP!",
|
|
40
|
+
"message": "Dette er en browserfunktion for udviklere.\\nHvis nogen beder dig om at indsætte noget her, er det snyd."
|
|
41
|
+
},
|
|
42
|
+
"fi": {
|
|
43
|
+
"title": "SEIS!",
|
|
44
|
+
"message": "Tämä on selaimen ominaisuus kehittäjille.\\nJos joku pyytää sinua liittämään jotain tähän, se on huijaus."
|
|
45
|
+
},
|
|
46
|
+
"ru": {
|
|
47
|
+
"title": "СТОП!",
|
|
48
|
+
"message": "Это функция браузера для разработчиков.\\nЕсли кто-то просит вставить сюда что-то — это мошенничество."
|
|
49
|
+
},
|
|
50
|
+
"uk": {
|
|
51
|
+
"title": "СТОП!",
|
|
52
|
+
"message": "Це функція браузера для розробників.\\nЯкщо хтось просить вставити щось сюди — це шахрайство."
|
|
53
|
+
},
|
|
54
|
+
"pl": {
|
|
55
|
+
"title": "STOP!",
|
|
56
|
+
"message": "To funkcja przeglądarki dla deweloperów.\\nJeśli ktoś prosi, abyś wkleił coś tutaj, to oszustwo."
|
|
57
|
+
},
|
|
58
|
+
"cs": {
|
|
59
|
+
"title": "STOP!",
|
|
60
|
+
"message": "Toto je funkce prohlížeče pro vývojáře.\\nPokud vás někdo požádá, abyste sem něco vložili, je to podvod."
|
|
61
|
+
},
|
|
62
|
+
"sk": {
|
|
63
|
+
"title": "STOP!",
|
|
64
|
+
"message": "Toto je funkcia prehliadača pre vývojárov.\\nAk vás niekto požiada, aby ste sem niečo vložili, je to podvod."
|
|
65
|
+
},
|
|
66
|
+
"hu": {
|
|
67
|
+
"title": "ÁLLJ!",
|
|
68
|
+
"message": "Ez egy böngészőfunkció fejlesztőknek.\\nHa valaki arra kér, hogy ide másolj valamit, az átverés."
|
|
69
|
+
},
|
|
70
|
+
"ro": {
|
|
71
|
+
"title": "STOP!",
|
|
72
|
+
"message": "Aceasta este o funcție a browserului pentru dezvoltatori.\\nDacă cineva îți cere să lipești ceva aici, este o înșelătorie."
|
|
73
|
+
},
|
|
74
|
+
"bg": {
|
|
75
|
+
"title": "СТОП!",
|
|
76
|
+
"message": "Това е функция на браузъра за разработчици.\\nАко някой ви помоли да поставите нещо тук, това е измама."
|
|
77
|
+
},
|
|
78
|
+
"tr": {
|
|
79
|
+
"title": "DUR!",
|
|
80
|
+
"message": "Bu, geliştiriciler için bir tarayıcı özelliğidir.\\nBirisi buraya bir şey yapıştırmanı isterse, bu bir dolandırıcılıktır."
|
|
81
|
+
},
|
|
82
|
+
"el": {
|
|
83
|
+
"title": "ΣΤΟΠ!",
|
|
84
|
+
"message": "Αυτή είναι μια λειτουργία του προγράμματος περιήγησης για προγραμματιστές.\\nΑν κάποιος σας ζητήσει να επικολλήσετε κάτι εδώ, είναι απάτη."
|
|
85
|
+
},
|
|
86
|
+
"ar": {
|
|
87
|
+
"title": "توقف!",
|
|
88
|
+
"message": "هذه ميزة في المتصفح للمطورين.\\nإذا طلب منك أحد لصق شيء هنا، فهذه عملية احتيال."
|
|
89
|
+
},
|
|
90
|
+
"he": {
|
|
91
|
+
"title": "עצור!",
|
|
92
|
+
"message": "זו תכונת דפדפן למפתחים.\\nאם מישהו מבקש ממך להדביק משהו כאן, זו הונאה."
|
|
93
|
+
},
|
|
94
|
+
"hi": {
|
|
95
|
+
"title": "रुको!",
|
|
96
|
+
"message": "यह डेवलपर्स के लिए ब्राउज़र की सुविधा है।\\nयदि कोई आपसे यहां कुछ पेस्ट करने को कहे, तो यह धोखाधड़ी है।"
|
|
97
|
+
},
|
|
98
|
+
"th": {
|
|
99
|
+
"title": "หยุด!",
|
|
100
|
+
"message": "นี่เป็นฟีเจอร์ของเบราว์เซอร์สำหรับนักพัฒนา\\nหากมีคนขอให้คุณวางบางอย่างที่นี่ นี่คือการหลอกลวง"
|
|
101
|
+
},
|
|
102
|
+
"vi": {
|
|
103
|
+
"title": "DỪNG!",
|
|
104
|
+
"message": "Đây là một tính năng của trình duyệt dành cho lập trình viên.\\nNếu ai đó yêu cầu bạn dán thứ gì đó vào đây, đó là lừa đảo."
|
|
105
|
+
},
|
|
106
|
+
"id": {
|
|
107
|
+
"title": "BERHENTI!",
|
|
108
|
+
"message": "Ini adalah fitur browser untuk pengembang.\\nJika seseorang meminta Anda menempelkan sesuatu di sini, itu penipuan."
|
|
109
|
+
},
|
|
110
|
+
"ms": {
|
|
111
|
+
"title": "BERHENTI!",
|
|
112
|
+
"message": "Ini adalah ciri pelayar untuk pembangun.\\nJika seseorang meminta anda menampal sesuatu di sini, itu penipuan."
|
|
113
|
+
},
|
|
114
|
+
"zh": {
|
|
115
|
+
"title": "停止!",
|
|
116
|
+
"message": "这是给开发者使用的浏览器功能。\\n如果有人让你在这里粘贴内容,那是诈骗。"
|
|
117
|
+
},
|
|
118
|
+
"ja": {
|
|
119
|
+
"title": "停止!",
|
|
120
|
+
"message": "これは開発者向けのブラウザー機能です。\\nここに何かを貼り付けるよう求められたら、それは詐欺です。"
|
|
121
|
+
},
|
|
122
|
+
"ko": {
|
|
123
|
+
"title": "멈춰!",
|
|
124
|
+
"message": "이것은 개발자를 위한 브라우저 기능입니다.\\n여기에 무언가를 붙여 넣으라고 요청받으면 사기입니다."
|
|
125
|
+
}
|
|
126
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "console-self-xss-warning",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Show a self-XSS warning in the browser console.",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "Hikasami",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/hksm-app/console-self-xss-warning.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/hksm-app/console-self-xss-warning/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/hksm-app/console-self-xss-warning",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"console-self-xss-warning",
|
|
20
|
+
"self-xss-warning",
|
|
21
|
+
"browser-console-warning",
|
|
22
|
+
"react-console-warning",
|
|
23
|
+
"nextjs-console-warning"
|
|
24
|
+
],
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"module": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"typescript": "^5.3.3"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function detectLanguage(fallback = "en"): string {
|
|
2
|
+
if (typeof navigator === "undefined") {
|
|
3
|
+
return fallback;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const raw =
|
|
7
|
+
(Array.isArray(navigator.languages) && navigator.languages[0]) ||
|
|
8
|
+
navigator.language ||
|
|
9
|
+
fallback;
|
|
10
|
+
|
|
11
|
+
if (!raw) {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const normalized = raw.toLowerCase().replace("_", "-");
|
|
16
|
+
return normalized.split("-")[0] || fallback;
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import defaultTranslations from "./translations.default.json";
|
|
2
|
+
import { detectLanguage } from "./detectLanguage";
|
|
3
|
+
|
|
4
|
+
export type Translation = {
|
|
5
|
+
title: string;
|
|
6
|
+
message: string;
|
|
7
|
+
titleStyle?: string;
|
|
8
|
+
messageStyle?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type Options = {
|
|
12
|
+
translations?: Record<string, Translation>;
|
|
13
|
+
forceLang?: string;
|
|
14
|
+
once?: boolean;
|
|
15
|
+
productionOnly?: boolean;
|
|
16
|
+
clearConsole?: boolean;
|
|
17
|
+
productionOnlyEnvKey?: string | string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TITLE_STYLE = "color:red;font-size:48px;font-weight:bold;";
|
|
21
|
+
const DEFAULT_MESSAGE_STYLE = "font-size:16px;";
|
|
22
|
+
const DEFAULT_SPAM_INTERVAL_MS = 2000;
|
|
23
|
+
const DEVTOOLS_SIZE_THRESHOLD_PX = 160;
|
|
24
|
+
|
|
25
|
+
const GLOBAL_ONCE_FLAG = "__consoleSelfXssWarningShown__";
|
|
26
|
+
|
|
27
|
+
let hasShown = false;
|
|
28
|
+
let spamIntervalId: number | undefined;
|
|
29
|
+
let spamPayload: WarningPayload | undefined;
|
|
30
|
+
|
|
31
|
+
type WarningPayload = {
|
|
32
|
+
title: string;
|
|
33
|
+
message: string;
|
|
34
|
+
titleStyle?: string;
|
|
35
|
+
messageStyle?: string;
|
|
36
|
+
clearConsole: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function getHasShown(): boolean {
|
|
40
|
+
if (hasShown) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (typeof globalThis !== "undefined") {
|
|
44
|
+
return Boolean((globalThis as Record<string, unknown>)[GLOBAL_ONCE_FLAG]);
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setHasShown(): void {
|
|
50
|
+
hasShown = true;
|
|
51
|
+
if (typeof globalThis !== "undefined") {
|
|
52
|
+
(globalThis as Record<string, unknown>)[GLOBAL_ONCE_FLAG] = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isDevtoolsLikelyOpen(): boolean {
|
|
57
|
+
if (typeof window === "undefined") {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
window.outerWidth - window.innerWidth > DEVTOOLS_SIZE_THRESHOLD_PX ||
|
|
63
|
+
window.outerHeight - window.innerHeight > DEVTOOLS_SIZE_THRESHOLD_PX
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isBrowser(): boolean {
|
|
68
|
+
return typeof window !== "undefined" && typeof console !== "undefined";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isProduction(): boolean {
|
|
72
|
+
const proc =
|
|
73
|
+
typeof globalThis !== "undefined"
|
|
74
|
+
? (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process
|
|
75
|
+
: undefined;
|
|
76
|
+
const nodeEnv =
|
|
77
|
+
proc && proc.env && typeof proc.env.NODE_ENV === "string"
|
|
78
|
+
? proc.env.NODE_ENV
|
|
79
|
+
: undefined;
|
|
80
|
+
|
|
81
|
+
if (nodeEnv) {
|
|
82
|
+
return nodeEnv === "production";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const metaEnv =
|
|
86
|
+
typeof import.meta !== "undefined"
|
|
87
|
+
? (import.meta as { env?: { MODE?: string; PROD?: boolean } }).env
|
|
88
|
+
: undefined;
|
|
89
|
+
|
|
90
|
+
if (metaEnv) {
|
|
91
|
+
if (typeof metaEnv.PROD === "boolean") {
|
|
92
|
+
return metaEnv.PROD;
|
|
93
|
+
}
|
|
94
|
+
if (typeof metaEnv.MODE === "string") {
|
|
95
|
+
return metaEnv.MODE === "production";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseEnvBoolean(value: unknown): boolean | undefined {
|
|
103
|
+
if (typeof value === "boolean") {
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
if (typeof value !== "string") {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const normalized = value.trim().toLowerCase();
|
|
110
|
+
if (["1", "true", "yes", "y", "on"].includes(normalized)) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (["0", "false", "no", "n", "off"].includes(normalized)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const DEFAULT_PRODUCTION_ONLY_ENV_KEYS = [
|
|
120
|
+
"CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY",
|
|
121
|
+
"VITE_CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY",
|
|
122
|
+
"NEXT_PUBLIC_CONSOLE_SELF_XSS_WARNING_PRODUCTION_ONLY"
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
function resolveEnvValue(
|
|
126
|
+
env: Record<string, unknown> | undefined,
|
|
127
|
+
keys: string[]
|
|
128
|
+
): unknown {
|
|
129
|
+
if (!env) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
if (key in env) {
|
|
134
|
+
return env[key];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getConfigProductionOnly(
|
|
141
|
+
keys: string | string[] | undefined
|
|
142
|
+
): boolean | undefined {
|
|
143
|
+
const keyList =
|
|
144
|
+
typeof keys === "string"
|
|
145
|
+
? [keys]
|
|
146
|
+
: Array.isArray(keys) && keys.length > 0
|
|
147
|
+
? keys
|
|
148
|
+
: DEFAULT_PRODUCTION_ONLY_ENV_KEYS;
|
|
149
|
+
const proc =
|
|
150
|
+
typeof globalThis !== "undefined"
|
|
151
|
+
? (globalThis as { process?: { env?: Record<string, unknown> } }).process
|
|
152
|
+
: undefined;
|
|
153
|
+
const nodeEnvValue = resolveEnvValue(proc && proc.env ? proc.env : undefined, keyList);
|
|
154
|
+
|
|
155
|
+
const metaEnv =
|
|
156
|
+
typeof import.meta !== "undefined"
|
|
157
|
+
? (import.meta as { env?: Record<string, unknown> }).env
|
|
158
|
+
: undefined;
|
|
159
|
+
const metaValue = resolveEnvValue(metaEnv, keyList);
|
|
160
|
+
|
|
161
|
+
return parseEnvBoolean(nodeEnvValue ?? metaValue);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function mergeTranslations(
|
|
165
|
+
overrides?: Record<string, Translation>
|
|
166
|
+
): Record<string, Translation> {
|
|
167
|
+
if (!overrides) {
|
|
168
|
+
return defaultTranslations as Record<string, Translation>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const merged: Record<string, Translation> = {
|
|
172
|
+
...(defaultTranslations as Record<string, Translation>),
|
|
173
|
+
...overrides
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return merged;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resolveTranslation(
|
|
180
|
+
translations: Record<string, Translation>,
|
|
181
|
+
lang: string
|
|
182
|
+
): Translation {
|
|
183
|
+
const fallback =
|
|
184
|
+
translations.en ||
|
|
185
|
+
(defaultTranslations as Record<string, Translation>).en || {
|
|
186
|
+
title: "STOP!",
|
|
187
|
+
message:
|
|
188
|
+
"This is a browser feature for developers.\nIf someone asks you to paste something here — it is a scam."
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const selected = translations[lang] || translations[lang.toLowerCase()];
|
|
192
|
+
if (!selected) {
|
|
193
|
+
return fallback;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const rawTitle = selected.title || fallback.title;
|
|
197
|
+
const rawMessage = selected.message || fallback.message;
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
title: rawTitle.replace(/\\n/g, "\n"),
|
|
201
|
+
message: rawMessage.replace(/\\n/g, "\n"),
|
|
202
|
+
titleStyle: selected.titleStyle,
|
|
203
|
+
messageStyle: selected.messageStyle
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function logWarning(payload: WarningPayload): void {
|
|
208
|
+
if (payload.clearConsole && typeof console.clear === "function") {
|
|
209
|
+
console.clear();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(`%c${payload.title}`, payload.titleStyle || DEFAULT_TITLE_STYLE);
|
|
213
|
+
console.log(
|
|
214
|
+
`%c${payload.message}`,
|
|
215
|
+
payload.messageStyle || DEFAULT_MESSAGE_STYLE
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function startSpam(payload: WarningPayload): void {
|
|
220
|
+
spamPayload = payload;
|
|
221
|
+
|
|
222
|
+
if (typeof window === "undefined") {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (spamIntervalId !== undefined) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (isDevtoolsLikelyOpen()) {
|
|
231
|
+
logWarning(payload);
|
|
232
|
+
setHasShown();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
spamIntervalId = window.setInterval(() => {
|
|
236
|
+
if (!spamPayload || !isDevtoolsLikelyOpen()) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
logWarning(spamPayload);
|
|
241
|
+
setHasShown();
|
|
242
|
+
}, DEFAULT_SPAM_INTERVAL_MS);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function showConsoleWarning(options: Options = {}): void {
|
|
246
|
+
if (!isBrowser()) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const {
|
|
251
|
+
translations: translationOverrides,
|
|
252
|
+
forceLang,
|
|
253
|
+
once = true,
|
|
254
|
+
productionOnly,
|
|
255
|
+
clearConsole = false,
|
|
256
|
+
productionOnlyEnvKey
|
|
257
|
+
} = options;
|
|
258
|
+
|
|
259
|
+
const resolvedProductionOnly =
|
|
260
|
+
productionOnly ?? getConfigProductionOnly(productionOnlyEnvKey) ?? false;
|
|
261
|
+
|
|
262
|
+
if (resolvedProductionOnly && !isProduction()) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (once && getHasShown()) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const translations = mergeTranslations(translationOverrides);
|
|
271
|
+
const lang = (forceLang || detectLanguage("en")).toLowerCase();
|
|
272
|
+
const { title, message, titleStyle, messageStyle } = resolveTranslation(
|
|
273
|
+
translations,
|
|
274
|
+
lang
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const payload: WarningPayload = {
|
|
278
|
+
title,
|
|
279
|
+
message,
|
|
280
|
+
titleStyle,
|
|
281
|
+
messageStyle,
|
|
282
|
+
clearConsole
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
if (!once) {
|
|
286
|
+
startSpam(payload);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
logWarning(payload);
|
|
291
|
+
|
|
292
|
+
setHasShown();
|
|
293
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en": {
|
|
3
|
+
"title": "STOP!",
|
|
4
|
+
"message": "This is a browser feature for developers.\\nIf someone asks you to paste something here — it is a scam."
|
|
5
|
+
},
|
|
6
|
+
"es": {
|
|
7
|
+
"title": "¡ALTO!",
|
|
8
|
+
"message": "Esta es una función del navegador para desarrolladores.\\nSi alguien te pide que pegues algo aquí, es una estafa."
|
|
9
|
+
},
|
|
10
|
+
"fr": {
|
|
11
|
+
"title": "STOP !",
|
|
12
|
+
"message": "Ceci est une fonctionnalité du navigateur pour les développeurs.\\nSi quelqu'un vous demande de coller quelque chose ici, c'est une arnaque."
|
|
13
|
+
},
|
|
14
|
+
"de": {
|
|
15
|
+
"title": "STOPP!",
|
|
16
|
+
"message": "Dies ist eine Browserfunktion für Entwickler.\\nWenn dich jemand bittet, hier etwas einzufügen, ist das Betrug."
|
|
17
|
+
},
|
|
18
|
+
"it": {
|
|
19
|
+
"title": "STOP!",
|
|
20
|
+
"message": "Questa è una funzione del browser per sviluppatori.\\nSe qualcuno ti chiede di incollare qualcosa qui, è una truffa."
|
|
21
|
+
},
|
|
22
|
+
"pt": {
|
|
23
|
+
"title": "PARE!",
|
|
24
|
+
"message": "Este é um recurso do navegador para desenvolvedores.\\nSe alguém pedir para você colar algo aqui, é um golpe."
|
|
25
|
+
},
|
|
26
|
+
"nl": {
|
|
27
|
+
"title": "STOP!",
|
|
28
|
+
"message": "Dit is een browserfunctie voor ontwikkelaars.\\nAls iemand je vraagt hier iets te plakken, is dat oplichting."
|
|
29
|
+
},
|
|
30
|
+
"sv": {
|
|
31
|
+
"title": "STOPP!",
|
|
32
|
+
"message": "Det här är en webbläsarfunktion för utvecklare.\\nOm någon ber dig klistra in något här är det en bluff."
|
|
33
|
+
},
|
|
34
|
+
"no": {
|
|
35
|
+
"title": "STOPP!",
|
|
36
|
+
"message": "Dette er en nettleserfunksjon for utviklere.\\nHvis noen ber deg lime inn noe her, er det svindel."
|
|
37
|
+
},
|
|
38
|
+
"da": {
|
|
39
|
+
"title": "STOP!",
|
|
40
|
+
"message": "Dette er en browserfunktion for udviklere.\\nHvis nogen beder dig om at indsætte noget her, er det snyd."
|
|
41
|
+
},
|
|
42
|
+
"fi": {
|
|
43
|
+
"title": "SEIS!",
|
|
44
|
+
"message": "Tämä on selaimen ominaisuus kehittäjille.\\nJos joku pyytää sinua liittämään jotain tähän, se on huijaus."
|
|
45
|
+
},
|
|
46
|
+
"ru": {
|
|
47
|
+
"title": "СТОП!",
|
|
48
|
+
"message": "Это функция браузера для разработчиков.\\nЕсли кто-то просит вставить сюда что-то — это мошенничество."
|
|
49
|
+
},
|
|
50
|
+
"uk": {
|
|
51
|
+
"title": "СТОП!",
|
|
52
|
+
"message": "Це функція браузера для розробників.\\nЯкщо хтось просить вставити щось сюди — це шахрайство."
|
|
53
|
+
},
|
|
54
|
+
"pl": {
|
|
55
|
+
"title": "STOP!",
|
|
56
|
+
"message": "To funkcja przeglądarki dla deweloperów.\\nJeśli ktoś prosi, abyś wkleił coś tutaj, to oszustwo."
|
|
57
|
+
},
|
|
58
|
+
"cs": {
|
|
59
|
+
"title": "STOP!",
|
|
60
|
+
"message": "Toto je funkce prohlížeče pro vývojáře.\\nPokud vás někdo požádá, abyste sem něco vložili, je to podvod."
|
|
61
|
+
},
|
|
62
|
+
"sk": {
|
|
63
|
+
"title": "STOP!",
|
|
64
|
+
"message": "Toto je funkcia prehliadača pre vývojárov.\\nAk vás niekto požiada, aby ste sem niečo vložili, je to podvod."
|
|
65
|
+
},
|
|
66
|
+
"hu": {
|
|
67
|
+
"title": "ÁLLJ!",
|
|
68
|
+
"message": "Ez egy böngészőfunkció fejlesztőknek.\\nHa valaki arra kér, hogy ide másolj valamit, az átverés."
|
|
69
|
+
},
|
|
70
|
+
"ro": {
|
|
71
|
+
"title": "STOP!",
|
|
72
|
+
"message": "Aceasta este o funcție a browserului pentru dezvoltatori.\\nDacă cineva îți cere să lipești ceva aici, este o înșelătorie."
|
|
73
|
+
},
|
|
74
|
+
"bg": {
|
|
75
|
+
"title": "СТОП!",
|
|
76
|
+
"message": "Това е функция на браузъра за разработчици.\\nАко някой ви помоли да поставите нещо тук, това е измама."
|
|
77
|
+
},
|
|
78
|
+
"tr": {
|
|
79
|
+
"title": "DUR!",
|
|
80
|
+
"message": "Bu, geliştiriciler için bir tarayıcı özelliğidir.\\nBirisi buraya bir şey yapıştırmanı isterse, bu bir dolandırıcılıktır."
|
|
81
|
+
},
|
|
82
|
+
"el": {
|
|
83
|
+
"title": "ΣΤΟΠ!",
|
|
84
|
+
"message": "Αυτή είναι μια λειτουργία του προγράμματος περιήγησης για προγραμματιστές.\\nΑν κάποιος σας ζητήσει να επικολλήσετε κάτι εδώ, είναι απάτη."
|
|
85
|
+
},
|
|
86
|
+
"ar": {
|
|
87
|
+
"title": "توقف!",
|
|
88
|
+
"message": "هذه ميزة في المتصفح للمطورين.\\nإذا طلب منك أحد لصق شيء هنا، فهذه عملية احتيال."
|
|
89
|
+
},
|
|
90
|
+
"he": {
|
|
91
|
+
"title": "עצור!",
|
|
92
|
+
"message": "זו תכונת דפדפן למפתחים.\\nאם מישהו מבקש ממך להדביק משהו כאן, זו הונאה."
|
|
93
|
+
},
|
|
94
|
+
"hi": {
|
|
95
|
+
"title": "रुको!",
|
|
96
|
+
"message": "यह डेवलपर्स के लिए ब्राउज़र की सुविधा है।\\nयदि कोई आपसे यहां कुछ पेस्ट करने को कहे, तो यह धोखाधड़ी है।"
|
|
97
|
+
},
|
|
98
|
+
"th": {
|
|
99
|
+
"title": "หยุด!",
|
|
100
|
+
"message": "นี่เป็นฟีเจอร์ของเบราว์เซอร์สำหรับนักพัฒนา\\nหากมีคนขอให้คุณวางบางอย่างที่นี่ นี่คือการหลอกลวง"
|
|
101
|
+
},
|
|
102
|
+
"vi": {
|
|
103
|
+
"title": "DỪNG!",
|
|
104
|
+
"message": "Đây là một tính năng của trình duyệt dành cho lập trình viên.\\nNếu ai đó yêu cầu bạn dán thứ gì đó vào đây, đó là lừa đảo."
|
|
105
|
+
},
|
|
106
|
+
"id": {
|
|
107
|
+
"title": "BERHENTI!",
|
|
108
|
+
"message": "Ini adalah fitur browser untuk pengembang.\\nJika seseorang meminta Anda menempelkan sesuatu di sini, itu penipuan."
|
|
109
|
+
},
|
|
110
|
+
"ms": {
|
|
111
|
+
"title": "BERHENTI!",
|
|
112
|
+
"message": "Ini adalah ciri pelayar untuk pembangun.\\nJika seseorang meminta anda menampal sesuatu di sini, itu penipuan."
|
|
113
|
+
},
|
|
114
|
+
"zh": {
|
|
115
|
+
"title": "停止!",
|
|
116
|
+
"message": "这是给开发者使用的浏览器功能。\\n如果有人让你在这里粘贴内容,那是诈骗。"
|
|
117
|
+
},
|
|
118
|
+
"ja": {
|
|
119
|
+
"title": "停止!",
|
|
120
|
+
"message": "これは開発者向けのブラウザー機能です。\\nここに何かを貼り付けるよう求められたら、それは詐欺です。"
|
|
121
|
+
},
|
|
122
|
+
"ko": {
|
|
123
|
+
"title": "멈춰!",
|
|
124
|
+
"message": "이것은 개발자를 위한 브라우저 기능입니다.\\n여기에 무언가를 붙여 넣으라고 요청받으면 사기입니다."
|
|
125
|
+
}
|
|
126
|
+
}
|