figranium 0.9.1 → 0.9.6

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/proxy-utils.js CHANGED
@@ -1,84 +1,84 @@
1
- const crypto = require('crypto');
2
-
3
- const ROTATION_MODES = new Set(['round-robin', 'random']);
4
-
5
- const normalizeServer = (raw) => {
6
- if (!raw) return '';
7
- let server = String(raw).trim();
8
- if (!server) return '';
9
- if (!server.includes('://')) {
10
- server = `http://${server}`;
11
- }
12
- return server;
13
- };
14
-
15
- const createProxyId = (seed) => {
16
- // SHA-256 is stronger than SHA-1 and avoids CodeQL warnings about weak cryptography.
17
- // The slice length remains 12 to keep IDs concise.
18
- const hash = crypto.createHash('sha256').update(String(seed)).digest('hex').slice(0, 12);
19
- return `proxy_${hash}`;
20
- };
21
-
22
- const normalizeProxy = (entry) => {
23
- if (!entry) return null;
24
- if (typeof entry === 'string') {
25
- let raw = entry.trim();
26
- if (!raw) return null;
27
- if (!raw.includes('://')) {
28
- raw = `http://${raw}`;
29
- }
30
- try {
31
- const parsed = new URL(raw);
32
- const server = `${parsed.protocol}//${parsed.host}`;
33
- const username = parsed.username ? decodeURIComponent(parsed.username) : undefined;
34
- const password = parsed.password ? decodeURIComponent(parsed.password) : undefined;
35
- return {
36
- id: createProxyId(`${server}|${username || ''}`),
37
- server,
38
- username,
39
- password
40
- };
41
- } catch {
42
- return null;
43
- }
44
- }
45
- if (typeof entry === 'object') {
46
- const serverRaw = entry.server || entry.url || entry.proxy;
47
- const server = normalizeServer(serverRaw);
48
- if (!server) return null;
49
- const username = entry.username || entry.user;
50
- const password = entry.password || entry.pass;
51
- const id = entry.id || createProxyId(`${server}|${username || ''}`);
52
- const isRotatingPool = !!entry.isRotatingPool;
53
- let estimatedPoolSize = undefined;
54
- if (entry.estimatedPoolSize !== undefined && entry.estimatedPoolSize !== null && entry.estimatedPoolSize !== '') {
55
- const parsedSize = parseInt(entry.estimatedPoolSize, 10);
56
- if (!Number.isNaN(parsedSize) && parsedSize > 0) {
57
- estimatedPoolSize = parsedSize;
58
- }
59
- }
60
- return {
61
- id,
62
- server,
63
- username,
64
- password,
65
- label: entry.label,
66
- isRotatingPool,
67
- estimatedPoolSize
68
- };
69
- }
70
- return null;
71
- };
72
-
73
- const normalizeRotationMode = (mode) => {
74
- if (ROTATION_MODES.has(mode)) return mode;
75
- return 'round-robin';
76
- };
77
-
78
- module.exports = {
79
- ROTATION_MODES,
80
- normalizeServer,
81
- createProxyId,
82
- normalizeProxy,
83
- normalizeRotationMode
84
- };
1
+ const crypto = require('crypto');
2
+
3
+ const ROTATION_MODES = new Set(['round-robin', 'random']);
4
+
5
+ const normalizeServer = (raw) => {
6
+ if (!raw) return '';
7
+ let server = String(raw).trim();
8
+ if (!server) return '';
9
+ if (!server.includes('://')) {
10
+ server = `http://${server}`;
11
+ }
12
+ return server;
13
+ };
14
+
15
+ const createProxyId = (seed) => {
16
+ // SHA-256 is stronger than SHA-1 and avoids CodeQL warnings about weak cryptography.
17
+ // The slice length remains 12 to keep IDs concise.
18
+ const hash = crypto.createHash('sha256').update(String(seed)).digest('hex').slice(0, 12);
19
+ return `proxy_${hash}`;
20
+ };
21
+
22
+ const normalizeProxy = (entry) => {
23
+ if (!entry) return null;
24
+ if (typeof entry === 'string') {
25
+ let raw = entry.trim();
26
+ if (!raw) return null;
27
+ if (!raw.includes('://')) {
28
+ raw = `http://${raw}`;
29
+ }
30
+ try {
31
+ const parsed = new URL(raw);
32
+ const server = `${parsed.protocol}//${parsed.host}`;
33
+ const username = parsed.username ? decodeURIComponent(parsed.username) : undefined;
34
+ const password = parsed.password ? decodeURIComponent(parsed.password) : undefined;
35
+ return {
36
+ id: createProxyId(`${server}|${username || ''}`),
37
+ server,
38
+ username,
39
+ password
40
+ };
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ if (typeof entry === 'object') {
46
+ const serverRaw = entry.server || entry.url || entry.proxy;
47
+ const server = normalizeServer(serverRaw);
48
+ if (!server) return null;
49
+ const username = entry.username || entry.user;
50
+ const password = entry.password || entry.pass;
51
+ const id = entry.id || createProxyId(`${server}|${username || ''}`);
52
+ const isRotatingPool = !!entry.isRotatingPool;
53
+ let estimatedPoolSize = undefined;
54
+ if (entry.estimatedPoolSize !== undefined && entry.estimatedPoolSize !== null && entry.estimatedPoolSize !== '') {
55
+ const parsedSize = parseInt(entry.estimatedPoolSize, 10);
56
+ if (!Number.isNaN(parsedSize) && parsedSize > 0) {
57
+ estimatedPoolSize = parsedSize;
58
+ }
59
+ }
60
+ return {
61
+ id,
62
+ server,
63
+ username,
64
+ password,
65
+ label: entry.label,
66
+ isRotatingPool,
67
+ estimatedPoolSize
68
+ };
69
+ }
70
+ return null;
71
+ };
72
+
73
+ const normalizeRotationMode = (mode) => {
74
+ if (ROTATION_MODES.has(mode)) return mode;
75
+ return 'round-robin';
76
+ };
77
+
78
+ module.exports = {
79
+ ROTATION_MODES,
80
+ normalizeServer,
81
+ createProxyId,
82
+ normalizeProxy,
83
+ normalizeRotationMode
84
+ };
package/public/novnc.html CHANGED
@@ -1,108 +1,108 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta
6
- name="viewport"
7
- content="width=device-width, initial-scale=1, maximum-scale=1"
8
- />
9
- <title>Headful Browser</title>
10
- <style>
11
- :root {
12
- color-scheme: dark;
13
- }
14
- html,
15
- body {
16
- width: 100%;
17
- height: 100%;
18
- margin: 0;
19
- background: #050505;
20
- overflow: hidden;
21
- font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
22
- }
23
- #screen {
24
- width: 100%;
25
- height: 100%;
26
- }
27
- #status {
28
- position: absolute;
29
- top: 12px;
30
- left: 12px;
31
- background: rgba(0, 0, 0, 0.6);
32
- border: 1px solid rgba(255, 255, 255, 0.1);
33
- padding: 6px 10px;
34
- border-radius: 10px;
35
- font-size: 11px;
36
- letter-spacing: 0.08em;
37
- text-transform: uppercase;
38
- color: #d1d5db;
39
- pointer-events: none;
40
- }
41
- #status.hidden {
42
- display: none;
43
- }
44
- </style>
45
- </head>
46
- <body>
47
- <div id="status">Connecting...</div>
48
- <div id="screen"></div>
49
- <script type="module">
50
- import RFB from '/novnc/core/rfb.js';
51
-
52
- const params = new URLSearchParams(window.location.search);
53
- const host = params.get('host') || window.location.hostname;
54
- const port = params.get('port') || window.location.port;
55
- const rawPath = params.get('path') || 'websockify';
56
- const path = rawPath.replace(/^\/+/, '');
57
- const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
58
- const hostPort = port ? `${host}:${port}` : host;
59
- const wsUrl = `${proto}://${hostPort}/${path}`;
60
-
61
- const statusEl = document.getElementById('status');
62
- const screen = document.getElementById('screen');
63
- const rfb = new RFB(screen, wsUrl);
64
- rfb.scaleViewport = true;
65
- rfb.resizeSession = true;
66
- rfb.showDotCursor = false;
67
- rfb.clipViewport = false;
68
-
69
- const sendKey = (keysym, code, down) => {
70
- if (typeof rfb.sendKey !== 'function') return;
71
- try {
72
- rfb.sendKey(keysym, code, down);
73
- } catch (err) {
74
- // ignore
75
- }
76
- };
77
-
78
- const clearModifiers = () => {
79
- const modifiers = [
80
- [0xFFE1, 'ShiftLeft'],
81
- [0xFFE2, 'ShiftRight'],
82
- [0xFFE3, 'ControlLeft'],
83
- [0xFFE4, 'ControlRight'],
84
- [0xFFE9, 'AltLeft'],
85
- [0xFFEA, 'AltRight'],
86
- [0xFFE7, 'MetaLeft'],
87
- [0xFFE8, 'MetaRight']
88
- ];
89
- modifiers.forEach(([keysym, code]) => sendKey(keysym, code, false));
90
- };
91
-
92
- window.addEventListener('focus', () => {
93
- clearModifiers();
94
- });
95
- window.addEventListener('blur', () => {
96
- clearModifiers();
97
- });
98
-
99
- rfb.addEventListener('connect', () => {
100
- statusEl.classList.add('hidden');
101
- });
102
- rfb.addEventListener('disconnect', () => {
103
- statusEl.classList.remove('hidden');
104
- statusEl.textContent = 'Disconnected';
105
- });
106
- </script>
107
- </body>
108
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1, maximum-scale=1"
8
+ />
9
+ <title>Headful Browser</title>
10
+ <style>
11
+ :root {
12
+ color-scheme: dark;
13
+ }
14
+ html,
15
+ body {
16
+ width: 100%;
17
+ height: 100%;
18
+ margin: 0;
19
+ background: #050505;
20
+ overflow: hidden;
21
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
22
+ }
23
+ #screen {
24
+ width: 100%;
25
+ height: 100%;
26
+ }
27
+ #status {
28
+ position: absolute;
29
+ top: 12px;
30
+ left: 12px;
31
+ background: rgba(0, 0, 0, 0.6);
32
+ border: 1px solid rgba(255, 255, 255, 0.1);
33
+ padding: 6px 10px;
34
+ border-radius: 10px;
35
+ font-size: 11px;
36
+ letter-spacing: 0.08em;
37
+ text-transform: uppercase;
38
+ color: #d1d5db;
39
+ pointer-events: none;
40
+ }
41
+ #status.hidden {
42
+ display: none;
43
+ }
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <div id="status">Connecting...</div>
48
+ <div id="screen"></div>
49
+ <script type="module">
50
+ import RFB from '/novnc/core/rfb.js';
51
+
52
+ const params = new URLSearchParams(window.location.search);
53
+ const host = params.get('host') || window.location.hostname;
54
+ const port = params.get('port') || window.location.port;
55
+ const rawPath = params.get('path') || 'websockify';
56
+ const path = rawPath.replace(/^\/+/, '');
57
+ const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
58
+ const hostPort = port ? `${host}:${port}` : host;
59
+ const wsUrl = `${proto}://${hostPort}/${path}`;
60
+
61
+ const statusEl = document.getElementById('status');
62
+ const screen = document.getElementById('screen');
63
+ const rfb = new RFB(screen, wsUrl);
64
+ rfb.scaleViewport = true;
65
+ rfb.resizeSession = true;
66
+ rfb.showDotCursor = false;
67
+ rfb.clipViewport = false;
68
+
69
+ const sendKey = (keysym, code, down) => {
70
+ if (typeof rfb.sendKey !== 'function') return;
71
+ try {
72
+ rfb.sendKey(keysym, code, down);
73
+ } catch (err) {
74
+ // ignore
75
+ }
76
+ };
77
+
78
+ const clearModifiers = () => {
79
+ const modifiers = [
80
+ [0xFFE1, 'ShiftLeft'],
81
+ [0xFFE2, 'ShiftRight'],
82
+ [0xFFE3, 'ControlLeft'],
83
+ [0xFFE4, 'ControlRight'],
84
+ [0xFFE9, 'AltLeft'],
85
+ [0xFFEA, 'AltRight'],
86
+ [0xFFE7, 'MetaLeft'],
87
+ [0xFFE8, 'MetaRight']
88
+ ];
89
+ modifiers.forEach(([keysym, code]) => sendKey(keysym, code, false));
90
+ };
91
+
92
+ window.addEventListener('focus', () => {
93
+ clearModifiers();
94
+ });
95
+ window.addEventListener('blur', () => {
96
+ clearModifiers();
97
+ });
98
+
99
+ rfb.addEventListener('connect', () => {
100
+ statusEl.classList.add('hidden');
101
+ });
102
+ rfb.addEventListener('disconnect', () => {
103
+ statusEl.classList.remove('hidden');
104
+ statusEl.textContent = 'Disconnected';
105
+ });
106
+ </script>
107
+ </body>
108
+ </html>