ownremote 0.0.0-stage → 1.0.1

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/src/config.js ADDED
@@ -0,0 +1,125 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+
5
+ const ROOT = path.resolve(__dirname, '..');
6
+
7
+ // Lokasi data (config, perangkat, log):
8
+ // 1. OWNREMOTE_DATA_DIR, bila diset (instance kedua, demo, pengujian)
9
+ // 2. folder data/ di samping aplikasi, bila sudah ada (clone git lama tetap memakai datanya)
10
+ // 3. %APPDATA%\OwnRemote: install lewat npm berada di node_modules yang terhapus saat update
11
+ function resolveDataDir() {
12
+ if (process.env.OWNREMOTE_DATA_DIR) return path.resolve(process.env.OWNREMOTE_DATA_DIR);
13
+ const legacy = path.join(ROOT, 'data');
14
+ if (fs.existsSync(path.join(legacy, 'config.json'))) return legacy;
15
+ const appData = process.env.APPDATA || path.join(require('os').homedir(), 'AppData', 'Roaming');
16
+ return path.join(appData, 'OwnRemote');
17
+ }
18
+
19
+ const DATA_DIR = resolveDataDir();
20
+ const CONFIG_FILE = path.join(DATA_DIR, 'config.json');
21
+ const DEVICES_FILE = path.join(DATA_DIR, 'devices.json');
22
+ const RUNTIME_FILE = path.join(DATA_DIR, 'runtime.json');
23
+ const LOG_FILE = path.join(DATA_DIR, 'server.log');
24
+
25
+ const DEFAULTS = {
26
+ port: 8787,
27
+ allowPairing: true,
28
+ startupDelay: 0, // detik menunggu sebelum server jalan saat Windows login
29
+ trayIcon: true, // ikon system tray di mode background
30
+ notifyOnConnect: true, // notifikasi tray saat HP terhubung
31
+ autoLockPairing: false, // kunci pairing otomatis setelah satu HP berhasil pairing
32
+ allowScreen: true, // HP boleh melihat preview layar (tetap harus dinyalakan manual dari HP)
33
+ tunnelMode: 'off', // Cloudflare Tunnel: off | quick (*.trycloudflare.com) | token (named tunnel)
34
+ tunnelToken: '', // token named tunnel dari Cloudflare Zero Trust (rahasia, tidak pernah dikirim ke HP/CLI status)
35
+ publicUrl: '', // URL publik named tunnel, mis. https://remote.domainanda.com
36
+ cloudflaredPath: '', // kosong = deteksi otomatis
37
+ };
38
+
39
+ // Pengaturan yang boleh diubah lewat Admin API saat server berjalan
40
+ const RUNTIME_SETTINGS = [
41
+ 'startupDelay', 'trayIcon', 'notifyOnConnect', 'autoLockPairing', 'allowScreen',
42
+ 'tunnelMode', 'tunnelToken', 'publicUrl', 'cloudflaredPath',
43
+ ];
44
+
45
+ function ensureDataDir() {
46
+ fs.mkdirSync(DATA_DIR, { recursive: true });
47
+ }
48
+
49
+ function readJson(file, fallback) {
50
+ try {
51
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
52
+ } catch {
53
+ return fallback;
54
+ }
55
+ }
56
+
57
+ function writeJson(file, data) {
58
+ ensureDataDir();
59
+ const tmp = `${file}.${process.pid}.tmp`;
60
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
61
+ fs.renameSync(tmp, file);
62
+ }
63
+
64
+ function newPairCode() {
65
+ return String(crypto.randomInt(0, 1_000_000)).padStart(6, '0');
66
+ }
67
+
68
+ function loadConfig() {
69
+ const cfg = { ...DEFAULTS, ...readJson(CONFIG_FILE, {}) };
70
+ let changed = false;
71
+ if (!/^\d{6}$/.test(cfg.pairCode || '')) {
72
+ cfg.pairCode = newPairCode();
73
+ changed = true;
74
+ }
75
+ if (!cfg.adminToken) {
76
+ cfg.adminToken = crypto.randomBytes(24).toString('hex');
77
+ changed = true;
78
+ }
79
+ if (changed) writeJson(CONFIG_FILE, cfg);
80
+ return cfg;
81
+ }
82
+
83
+ function saveConfig(cfg) {
84
+ writeJson(CONFIG_FILE, cfg);
85
+ }
86
+
87
+ function loadDevices() {
88
+ const list = readJson(DEVICES_FILE, []);
89
+ return Array.isArray(list) ? list : [];
90
+ }
91
+
92
+ function saveDevices(devices) {
93
+ writeJson(DEVICES_FILE, devices);
94
+ }
95
+
96
+ function readRuntime() {
97
+ return readJson(RUNTIME_FILE, null);
98
+ }
99
+
100
+ function writeRuntime(info) {
101
+ writeJson(RUNTIME_FILE, info);
102
+ }
103
+
104
+ function clearRuntime(pid = process.pid) {
105
+ const rt = readRuntime();
106
+ if (rt && rt.pid === pid) {
107
+ try { fs.unlinkSync(RUNTIME_FILE); } catch { /* sudah tidak ada */ }
108
+ }
109
+ }
110
+
111
+ module.exports = {
112
+ ROOT,
113
+ DATA_DIR,
114
+ LOG_FILE,
115
+ RUNTIME_SETTINGS,
116
+ ensureDataDir,
117
+ newPairCode,
118
+ loadConfig,
119
+ saveConfig,
120
+ loadDevices,
121
+ saveDevices,
122
+ readRuntime,
123
+ writeRuntime,
124
+ clearRuntime,
125
+ };
package/src/daemon.js ADDED
@@ -0,0 +1,43 @@
1
+ // Entry point mode background. Dijalankan tersembunyi (tanpa jendela) lewat wscript + launcher .vbs,
2
+ // baik dari menu CLI maupun dari folder Startup Windows. Semua output ditulis ke data/server.log.
3
+ const fs = require('fs');
4
+ const config = require('./config');
5
+ const { createServer } = require('./server');
6
+
7
+ config.ensureDataDir();
8
+
9
+ // rotasi log sederhana: simpan maksimal ~1 MB
10
+ try {
11
+ if (fs.statSync(config.LOG_FILE).size > 1024 * 1024) fs.renameSync(config.LOG_FILE, `${config.LOG_FILE}.old`);
12
+ } catch { /* log belum ada */ }
13
+
14
+ const out = fs.createWriteStream(config.LOG_FILE, { flags: 'a' });
15
+ const log = (msg) => out.write(`[${new Date().toLocaleString('en-GB')}] ${msg}\n`);
16
+
17
+ process.on('uncaughtException', (err) => log(`ERROR: ${err.stack || err}`));
18
+ process.on('unhandledRejection', (err) => log(`ERROR: ${err?.stack || err}`));
19
+
20
+ const server = createServer({
21
+ mode: 'background',
22
+ log,
23
+ onShutdown: () => out.end(() => process.exit(0)),
24
+ });
25
+
26
+ const shutdown = () => server.stop().then(() => out.end(() => process.exit(0)));
27
+ process.on('SIGINT', shutdown);
28
+ process.on('SIGTERM', shutdown);
29
+
30
+ // Saat dijalankan dari folder Startup Windows, tunggu sesuai "startup delay" (mis. menunggu Wi-Fi tersambung)
31
+ const delay = process.argv.includes('--startup') ? config.loadConfig().startupDelay || 0 : 0;
32
+ if (delay > 0) log(`Launched at Windows sign-in, waiting ${delay}s before starting...`);
33
+
34
+ new Promise((r) => setTimeout(r, delay * 1000))
35
+ .then(() => server.start())
36
+ .then(({ addresses, port }) => {
37
+ const urls = addresses.map((a) => `http://${a.address}:${port}`).join(', ') || '(no network)';
38
+ log(`Open on your phone: ${urls}`);
39
+ })
40
+ .catch((err) => {
41
+ log(`Failed to start: ${err.message}`);
42
+ out.end(() => process.exit(1));
43
+ });
@@ -0,0 +1,163 @@
1
+ # Helper input (mouse + keyboard). Dijalankan oleh src/input.js, membaca perintah per baris dari stdin:
2
+ # m <dx> <dy> gerak relatif (pixel)
3
+ # c <l|r|m> klik
4
+ # d <l|r|m> tekan tombol mouse (untuk drag)
5
+ # u <l|r|m> lepas tombol mouse
6
+ # s <delta> scroll vertikal (120 = 1 notch, positif = ke atas)
7
+ # h <delta> scroll horizontal (positif = ke kanan)
8
+ # t <base64 utf8> ketik teks (Unicode; \n = Enter, \t = Tab)
9
+ # k <vk> <mods> tekan tombol virtual-key dengan modifier
10
+ # kc <mods> <code> tekan karakter (code point) dengan modifier, mis. Ctrl+C
11
+ # mods = bitmask: 1 Ctrl, 2 Alt, 4 Shift, 8 Win
12
+ # Proses berhenti otomatis saat stdin ditutup (server mati).
13
+ $ErrorActionPreference = 'Stop'
14
+
15
+ Add-Type -TypeDefinition @"
16
+ using System;
17
+ using System.Runtime.InteropServices;
18
+ using System.Text;
19
+
20
+ public static class OwnRemoteInput {
21
+ [StructLayout(LayoutKind.Sequential)]
22
+ public struct POINT { public int X; public int Y; }
23
+
24
+ [StructLayout(LayoutKind.Sequential)]
25
+ struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; }
26
+
27
+ [StructLayout(LayoutKind.Sequential)]
28
+ struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; }
29
+
30
+ [StructLayout(LayoutKind.Explicit)]
31
+ struct InputUnion { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; }
32
+
33
+ [StructLayout(LayoutKind.Sequential)]
34
+ struct INPUT { public uint type; public InputUnion u; }
35
+
36
+ [DllImport("user32.dll")] static extern bool GetCursorPos(out POINT p);
37
+ [DllImport("user32.dll")] static extern bool SetCursorPos(int x, int y);
38
+ [DllImport("user32.dll")] static extern void mouse_event(uint flags, int dx, int dy, int data, UIntPtr extra);
39
+ [DllImport("user32.dll")] static extern bool SetProcessDPIAware();
40
+ [DllImport("user32.dll")] static extern bool SetProcessDpiAwarenessContext(IntPtr value);
41
+
42
+ // Per-Monitor V2: koordinat cursor & monitor dalam piksel fisik, akurat juga di monitor dengan scaling berbeda
43
+ static void MakeDpiAware() {
44
+ try { if (SetProcessDpiAwarenessContext(new IntPtr(-4))) return; } catch { }
45
+ try { SetProcessDPIAware(); } catch { }
46
+ }
47
+ [DllImport("user32.dll", SetLastError = true)] static extern uint SendInput(uint n, INPUT[] inputs, int size);
48
+ [DllImport("user32.dll")] static extern uint MapVirtualKey(uint code, uint mapType);
49
+ [DllImport("user32.dll")] static extern short VkKeyScan(char ch);
50
+
51
+ const uint LEFTDOWN = 0x02, LEFTUP = 0x04, RIGHTDOWN = 0x08, RIGHTUP = 0x10;
52
+ const uint MIDDLEDOWN = 0x20, MIDDLEUP = 0x40, WHEEL = 0x800, HWHEEL = 0x1000;
53
+
54
+ const uint INPUT_KEYBOARD = 1;
55
+ const uint KEY_EXTENDED = 0x1, KEY_UP = 0x2, KEY_UNICODE = 0x4;
56
+ const ushort VK_SHIFT = 0x10, VK_CONTROL = 0x11, VK_MENU = 0x12, VK_LWIN = 0x5B;
57
+ const ushort VK_RETURN = 0x0D, VK_TAB = 0x09;
58
+
59
+ static uint Down(string b) { return b == "r" ? RIGHTDOWN : b == "m" ? MIDDLEDOWN : LEFTDOWN; }
60
+ static uint Up(string b) { return b == "r" ? RIGHTUP : b == "m" ? MIDDLEUP : LEFTUP; }
61
+
62
+ static bool IsExtended(ushort vk) {
63
+ // panah, Home/End, PgUp/PgDn, Insert/Delete, tombol Windows
64
+ return (vk >= 0x21 && vk <= 0x28) || vk == 0x2D || vk == 0x2E || vk == 0x5B || vk == 0x5C;
65
+ }
66
+
67
+ static INPUT Vk(ushort vk, bool up) {
68
+ var i = new INPUT();
69
+ i.type = INPUT_KEYBOARD;
70
+ i.u.ki.wVk = vk;
71
+ i.u.ki.wScan = (ushort)MapVirtualKey(vk, 0);
72
+ i.u.ki.dwFlags = (up ? KEY_UP : 0) | (IsExtended(vk) ? KEY_EXTENDED : 0);
73
+ return i;
74
+ }
75
+
76
+ static INPUT Unicode(char ch, bool up) {
77
+ var i = new INPUT();
78
+ i.type = INPUT_KEYBOARD;
79
+ i.u.ki.wScan = ch;
80
+ i.u.ki.dwFlags = KEY_UNICODE | (up ? KEY_UP : 0);
81
+ return i;
82
+ }
83
+
84
+ static void Send(INPUT[] inputs) {
85
+ if (inputs.Length > 0) SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
86
+ }
87
+
88
+ static ushort[] ModKeys(int mods) {
89
+ var list = new System.Collections.Generic.List<ushort>();
90
+ if ((mods & 1) != 0) list.Add(VK_CONTROL);
91
+ if ((mods & 2) != 0) list.Add(VK_MENU);
92
+ if ((mods & 4) != 0) list.Add(VK_SHIFT);
93
+ if ((mods & 8) != 0) list.Add(VK_LWIN);
94
+ return list.ToArray();
95
+ }
96
+
97
+ static void PressKey(ushort vk, int mods) {
98
+ var seq = new System.Collections.Generic.List<INPUT>();
99
+ var modKeys = ModKeys(mods);
100
+ foreach (var m in modKeys) seq.Add(Vk(m, false));
101
+ seq.Add(Vk(vk, false));
102
+ seq.Add(Vk(vk, true));
103
+ for (int i = modKeys.Length - 1; i >= 0; i--) seq.Add(Vk(modKeys[i], true));
104
+ Send(seq.ToArray());
105
+ }
106
+
107
+ static void TypeText(string text) {
108
+ var seq = new System.Collections.Generic.List<INPUT>();
109
+ foreach (char ch in text.Replace("\r\n", "\n")) {
110
+ if (ch == '\n' || ch == '\r') { seq.Add(Vk(VK_RETURN, false)); seq.Add(Vk(VK_RETURN, true)); }
111
+ else if (ch == '\t') { seq.Add(Vk(VK_TAB, false)); seq.Add(Vk(VK_TAB, true)); }
112
+ else { seq.Add(Unicode(ch, false)); seq.Add(Unicode(ch, true)); }
113
+ }
114
+ Send(seq.ToArray());
115
+ }
116
+
117
+ // Karakter + modifier (mis. Ctrl+C): cari virtual-key sesuai layout keyboard aktif
118
+ static void PressChar(int mods, char ch) {
119
+ short scan = VkKeyScan(ch);
120
+ if (scan == -1) { TypeText(ch.ToString()); return; }
121
+ int shiftState = (scan >> 8) & 0xFF;
122
+ int extra = ((shiftState & 1) != 0 ? 4 : 0) | ((shiftState & 2) != 0 ? 1 : 0) | ((shiftState & 4) != 0 ? 2 : 0);
123
+ PressKey((ushort)(scan & 0xFF), mods | extra);
124
+ }
125
+
126
+ public static void Run() {
127
+ MakeDpiAware();
128
+ Console.Out.WriteLine("ready");
129
+ Console.Out.Flush();
130
+
131
+ string line;
132
+ while ((line = Console.In.ReadLine()) != null) {
133
+ try {
134
+ string[] a = line.Trim().Split(' ');
135
+ switch (a[0]) {
136
+ case "m": {
137
+ POINT p;
138
+ GetCursorPos(out p);
139
+ SetCursorPos(p.X + int.Parse(a[1]), p.Y + int.Parse(a[2]));
140
+ break;
141
+ }
142
+ case "c":
143
+ mouse_event(Down(a[1]), 0, 0, 0, UIntPtr.Zero);
144
+ mouse_event(Up(a[1]), 0, 0, 0, UIntPtr.Zero);
145
+ break;
146
+ case "d": mouse_event(Down(a[1]), 0, 0, 0, UIntPtr.Zero); break;
147
+ case "u": mouse_event(Up(a[1]), 0, 0, 0, UIntPtr.Zero); break;
148
+ case "s": mouse_event(WHEEL, 0, 0, int.Parse(a[1]), UIntPtr.Zero); break;
149
+ case "h": mouse_event(HWHEEL, 0, 0, int.Parse(a[1]), UIntPtr.Zero); break;
150
+ case "t": TypeText(Encoding.UTF8.GetString(Convert.FromBase64String(a[1]))); break;
151
+ case "k": PressKey((ushort)int.Parse(a[1]), int.Parse(a[2])); break;
152
+ case "kc": PressChar(int.Parse(a[1]), (char)int.Parse(a[2])); break;
153
+ case "q": return;
154
+ }
155
+ } catch (Exception e) {
156
+ Console.Error.WriteLine(e.Message);
157
+ }
158
+ }
159
+ }
160
+ }
161
+ "@
162
+
163
+ [OwnRemoteInput]::Run()
package/src/input.js ADDED
@@ -0,0 +1,152 @@
1
+ const { spawn } = require('child_process');
2
+ const path = require('path');
3
+
4
+ const BUTTONS = new Set(['l', 'r', 'm']);
5
+
6
+ // Nama tombol yang boleh dikirim dari HP -> virtual-key code Windows
7
+ const KEYS = {
8
+ enter: 0x0d, backspace: 0x08, tab: 0x09, esc: 0x1b, space: 0x20,
9
+ left: 0x25, up: 0x26, right: 0x27, down: 0x28,
10
+ home: 0x24, end: 0x23, pageup: 0x21, pagedown: 0x22, insert: 0x2d, delete: 0x2e,
11
+ win: 0x5b, menu: 0x5d, printscreen: 0x2c,
12
+ volumeup: 0xaf, volumedown: 0xae, mute: 0xad, playpause: 0xb3, next: 0xb0, prev: 0xb1,
13
+ };
14
+ for (let i = 1; i <= 12; i++) KEYS[`f${i}`] = 0x6f + i;
15
+
16
+ const MAX_TEXT = 500;
17
+
18
+ // Mengendalikan mouse & keyboard lewat proses PowerShell persisten (input-helper.ps1) yang memanggil Win32 API.
19
+ // Tanpa native module, jadi tidak perlu Visual Studio Build Tools.
20
+ class InputController {
21
+ constructor(log = () => {}) {
22
+ this.log = log;
23
+ this.proc = null;
24
+ this.ready = false;
25
+ this.stopped = false;
26
+ this.restartTimer = null;
27
+ // sisa pecahan pixel/scroll agar gerakan halus tidak hilang saat dibulatkan
28
+ this.fx = 0;
29
+ this.fy = 0;
30
+ this.fs = 0;
31
+ this.fh = 0;
32
+ }
33
+
34
+ start() {
35
+ this.stopped = false;
36
+ const script = path.join(__dirname, 'input-helper.ps1');
37
+ const proc = spawn(
38
+ 'powershell.exe',
39
+ ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', script],
40
+ { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] },
41
+ );
42
+ this.proc = proc;
43
+ this.ready = false;
44
+
45
+ proc.stdout.on('data', (buf) => {
46
+ if (!this.ready && String(buf).includes('ready')) {
47
+ this.ready = true;
48
+ this.log('Input controller ready (mouse + keyboard).');
49
+ }
50
+ });
51
+ proc.stderr.on('data', (buf) => {
52
+ const msg = String(buf).trim();
53
+ if (msg) this.log(`Input helper: ${msg}`);
54
+ });
55
+ proc.stdin.on('error', () => { /* proses sudah mati, ditangani di 'exit' */ });
56
+ proc.on('error', (err) => this.log(`Failed to launch PowerShell: ${err.message}`));
57
+ proc.on('exit', (code) => {
58
+ if (this.proc === proc) {
59
+ this.proc = null;
60
+ this.ready = false;
61
+ }
62
+ if (!this.stopped) {
63
+ this.log(`Input helper exited (code ${code}), restarting in 2 seconds...`);
64
+ this.restartTimer = setTimeout(() => this.start(), 2000);
65
+ }
66
+ });
67
+ }
68
+
69
+ stop() {
70
+ this.stopped = true;
71
+ clearTimeout(this.restartTimer);
72
+ if (this.proc) {
73
+ try { this.proc.stdin.end('q\n'); } catch { /* abaikan */ }
74
+ const p = this.proc;
75
+ setTimeout(() => { if (p.exitCode === null) p.kill(); }, 1000).unref();
76
+ this.proc = null;
77
+ }
78
+ }
79
+
80
+ send(line) {
81
+ if (this.proc && this.proc.stdin.writable) this.proc.stdin.write(`${line}\n`);
82
+ }
83
+
84
+ move(dx, dy) {
85
+ if (!Number.isFinite(dx) || !Number.isFinite(dy)) return;
86
+ this.fx += clamp(dx, -2000, 2000);
87
+ this.fy += clamp(dy, -2000, 2000);
88
+ const ix = Math.trunc(this.fx);
89
+ const iy = Math.trunc(this.fy);
90
+ if (ix === 0 && iy === 0) return;
91
+ this.fx -= ix;
92
+ this.fy -= iy;
93
+ this.send(`m ${ix} ${iy}`);
94
+ }
95
+
96
+ scroll(dy, dx = 0) {
97
+ if (Number.isFinite(dy) && dy !== 0) {
98
+ this.fs += clamp(dy, -1200, 1200);
99
+ const v = Math.trunc(this.fs);
100
+ if (v !== 0) {
101
+ this.fs -= v;
102
+ this.send(`s ${v}`);
103
+ }
104
+ }
105
+ if (Number.isFinite(dx) && dx !== 0) {
106
+ this.fh += clamp(dx, -1200, 1200);
107
+ const h = Math.trunc(this.fh);
108
+ if (h !== 0) {
109
+ this.fh -= h;
110
+ this.send(`h ${h}`);
111
+ }
112
+ }
113
+ }
114
+
115
+ click(button) {
116
+ if (BUTTONS.has(button)) this.send(`c ${button}`);
117
+ }
118
+
119
+ down(button) {
120
+ if (BUTTONS.has(button)) this.send(`d ${button}`);
121
+ }
122
+
123
+ up(button) {
124
+ if (BUTTONS.has(button)) this.send(`u ${button}`);
125
+ }
126
+
127
+ /** Ketik teks Unicode apa adanya ('\n' = Enter). */
128
+ typeText(text) {
129
+ const s = String(text ?? '').slice(0, MAX_TEXT);
130
+ if (s) this.send(`t ${Buffer.from(s, 'utf8').toString('base64')}`);
131
+ }
132
+
133
+ /** Tekan tombol bernama (lihat KEYS) dengan modifier bitmask: 1 Ctrl, 2 Alt, 4 Shift, 8 Win. */
134
+ key(name, mods = 0) {
135
+ const vk = KEYS[name];
136
+ if (vk) this.send(`k ${vk} ${mods & 15}`);
137
+ }
138
+
139
+ /** Tekan satu karakter dengan modifier, mis. ('c', 1) = Ctrl+C. */
140
+ keyChar(char, mods = 0) {
141
+ const code = String(char ?? '').charCodeAt(0);
142
+ if (!code) return;
143
+ if ((mods & 15) === 0) this.typeText(char);
144
+ else this.send(`kc ${mods & 15} ${code}`);
145
+ }
146
+ }
147
+
148
+ function clamp(v, min, max) {
149
+ return Math.min(max, Math.max(min, v));
150
+ }
151
+
152
+ module.exports = { InputController, BUTTONS, KEYS };
@@ -0,0 +1,13 @@
1
+ ' OwnRemote: start the background server without a console window.
2
+ ' Used by the "Start with Windows" registry entry (HKCU\Software\Microsoft\Windows\CurrentVersion\Run).
3
+ ' Usage: wscript.exe launch-hidden.vbs "<node.exe>" "<daemon.js>" [--startup]
4
+ Option Explicit
5
+ Dim args, command, i
6
+ Set args = WScript.Arguments
7
+ If args.Count < 2 Then WScript.Quit 1
8
+ command = Chr(34) & args(0) & Chr(34) & " " & Chr(34) & args(1) & Chr(34)
9
+ For i = 2 To args.Count - 1
10
+ command = command & " " & args(i)
11
+ Next
12
+ ' 0 = no window, False = do not wait
13
+ CreateObject("WScript.Shell").Run command, 0, False
package/src/logo.js ADDED
@@ -0,0 +1,137 @@
1
+ // Logo ASCII untuk CLI: panah cursor + tulisan "OWN REMOTE".
2
+ // Badan huruf memakai satu karakter padat ('#') supaya bentuknya jelas, bayangan '=' memberi kesan timbul,
3
+ // dan nuansa "kode simbol" ada di panah serta garis aksen.
4
+
5
+ const FONT = {
6
+ R: ['######.', '##...##', '##...##', '######.', '##..##.', '##...##'],
7
+ E: ['#######', '##.....', '#####..', '##.....', '##.....', '#######'],
8
+ M: ['##...##', '###.###', '#######', '##.#.##', '##...##', '##...##'],
9
+ O: ['.#####.', '##...##', '##...##', '##...##', '##...##', '.#####.'],
10
+ T: ['######', '..##..', '..##..', '..##..', '..##..', '..##..'],
11
+ C: ['.######', '##.....', '##.....', '##.....', '##.....', '.######'],
12
+ U: ['##...##', '##...##', '##...##', '##...##', '##...##', '.#####.'],
13
+ S: ['.######', '##.....', '.#####.', '.....##', '.....##', '######.'],
14
+ W: ['##...##', '##...##', '##.#.##', '#######', '###.###', '##...##'],
15
+ N: ['##...##', '###..##', '####.##', '##.####', '##..###', '##...##'],
16
+ };
17
+
18
+ // o = garis tepi, f = isian
19
+ const ARROW = [
20
+ 'o',
21
+ 'oo',
22
+ 'ofo',
23
+ 'offo',
24
+ 'offfo',
25
+ 'offffo',
26
+ 'offfffo',
27
+ 'offffffo',
28
+ 'offfoooo',
29
+ 'ofooffo',
30
+ 'oo offo',
31
+ 'o offo',
32
+ ' oo',
33
+ ];
34
+
35
+ const LETTER_GAP = 2;
36
+ const FILL = '#';
37
+ const SHADOW = '=';
38
+ const ACCENT = '#=@!$*)(';
39
+
40
+ // Susun satu kata menjadi grid 2D: 'X' = badan huruf, 'S' = bayangan, ' ' = kosong
41
+ function wordGrid(word) {
42
+ const height = 6;
43
+ const rows = Array.from({ length: height + 1 }, () => []);
44
+ let x = 0;
45
+ for (const ch of word) {
46
+ const glyph = FONT[ch];
47
+ glyph.forEach((line, y) => {
48
+ [...line].forEach((cell, i) => {
49
+ if (cell === '#') rows[y][x + i] = 'X';
50
+ });
51
+ });
52
+ x += glyph[0].length + LETTER_GAP;
53
+ }
54
+ const width = x - LETTER_GAP + 1;
55
+ // bayangan: geser 1 ke kanan-bawah, hanya di sel yang kosong
56
+ for (let y = height - 1; y >= 0; y--) {
57
+ for (let i = width - 1; i >= 0; i--) {
58
+ if (rows[y][i] === 'X' && rows[y + 1][i + 1] !== 'X') rows[y + 1][i + 1] = 'S';
59
+ }
60
+ }
61
+ return rows.map((r) => Array.from({ length: width }, (_, i) => r[i] || ' '));
62
+ }
63
+
64
+ function palette() {
65
+ const rich = process.stdout.hasColors?.(256);
66
+ const fg = (n) => `\x1b[38;5;${n}m`;
67
+ return {
68
+ rich,
69
+ bold: '\x1b[1m',
70
+ reset: '\x1b[0m',
71
+ dim: '\x1b[90m',
72
+ shadow: rich ? fg(238) : '\x1b[90m',
73
+ arrowEdge: rich ? fg(255) : '\x1b[97m',
74
+ arrowFill: rich ? fg(191) : '\x1b[93m',
75
+ // gradasi per baris: cyan -> hijau limau
76
+ textRows: rich ? [51, 51, 50, 49, 48, 84, 120, 156, 155, 154, 190, 190, 226, 226] : null,
77
+ word: ['\x1b[96m', '\x1b[92m'],
78
+ fg,
79
+ };
80
+ }
81
+
82
+ function paintGrid(grid, colorOf, p) {
83
+ return grid.map((row, y) => {
84
+ let out = '';
85
+ let current = '';
86
+ for (const cell of row) {
87
+ let color = '';
88
+ let ch = ' ';
89
+ if (cell === 'X') { color = p.bold + colorOf(y); ch = FILL; }
90
+ else if (cell === 'S') { color = p.reset + p.shadow; ch = SHADOW; }
91
+ if (ch !== ' ' && color !== current) { out += p.reset + color; current = color; }
92
+ out += ch;
93
+ }
94
+ return out + p.reset;
95
+ });
96
+ }
97
+
98
+ /** Logo besar untuk menu utama. */
99
+ function bigLogo(indent = ' ') {
100
+ const p = palette();
101
+ const top = wordGrid('OWN');
102
+ const bottom = wordGrid('REMOTE');
103
+ const color = (offset, wordIdx) => (y) => (p.rich ? p.fg(p.textRows[offset + y]) : p.word[wordIdx]);
104
+ const text = [
105
+ ...paintGrid(top, color(0, 0), p), // 7 baris (6 + bayangan)
106
+ ...paintGrid(bottom, color(7, 1), p), // 7 baris
107
+ ];
108
+
109
+ const arrow = ARROW.map((line) =>
110
+ [...line.padEnd(9)].map((c) => (c === 'o' ? `${p.arrowEdge}@` : c === 'f' ? `${p.arrowFill}#` : ' ')).join('') + p.reset,
111
+ );
112
+ // panah 13 baris, teks 14 baris: geser panah 1 baris ke bawah agar seimbang
113
+ const lines = [];
114
+ for (let i = 0; i < text.length; i++) {
115
+ const a = i >= 1 && i - 1 < arrow.length ? arrow[i - 1] : ' '.repeat(9);
116
+ lines.push(`${indent}${a} ${text[i]}`);
117
+ }
118
+
119
+ const width = Math.max(top[0].length, bottom[0].length);
120
+ const accent = ACCENT.repeat(Math.ceil(width / ACCENT.length)).slice(0, width);
121
+ const tagline = 'control your PC from your phone';
122
+ const left = Math.floor((width - tagline.length) / 2);
123
+ lines.push(`${indent}${' '.repeat(13)}${p.shadow}${accent}${p.reset}`);
124
+ lines.push(`${indent}${' '.repeat(13 + Math.max(0, left))}${p.dim}${tagline}${p.reset}`);
125
+ return lines.join('\n');
126
+ }
127
+
128
+ /** Header ringkas (1 baris) untuk sub-menu. */
129
+ function smallLogo(subtitle, indent = ' ') {
130
+ const p = palette();
131
+ const accent = p.rich ? p.fg(191) : '\x1b[93m';
132
+ const title = p.rich ? `${p.bold}${p.fg(51)}` : '\x1b[1m\x1b[96m';
133
+ const sub = subtitle ? ` ${p.dim}::${p.reset} \x1b[33m${subtitle}${p.reset}` : '';
134
+ return `${indent}${accent}#=@>${p.reset} ${title}OWNREMOTE${p.reset} ${accent}<@=#${p.reset}${sub}`;
135
+ }
136
+
137
+ module.exports = { bigLogo, smallLogo };