swayrouter 1.0.1 → 1.0.2

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/README.id.md CHANGED
@@ -149,12 +149,17 @@ Kalau mau ngembangin dashboard, jalanin `bun run dev` di dalam folder
149
149
 
150
150
  ### Docker — terisolasi dan datanya persistent
151
151
 
152
+ Gunakan image publik dari Docker Hub:
153
+
152
154
  ```bash
153
- docker build -t swayrouter:latest .
155
+ docker pull envielxyz/swayrouter:latest
154
156
  docker compose up -d
155
157
  docker compose ps
156
158
  ```
157
159
 
160
+ Kalau ingin build image dari source, jalankan `docker build -t envielxyz/swayrouter:latest .`
161
+ sebelum menjalankan Compose.
162
+
158
163
  Docker juga membuat secret persistent di data volume. Copy `.env.example`
159
164
  menjadi `.env` hanya kalau kamu ingin memberi override deployment sendiri.
160
165
 
package/README.md CHANGED
@@ -149,12 +149,17 @@ second terminal.
149
149
 
150
150
  ### Docker — isolated and persistent
151
151
 
152
+ Use the public image from Docker Hub:
153
+
152
154
  ```bash
153
- docker build -t swayrouter:latest .
155
+ docker pull envielxyz/swayrouter:latest
154
156
  docker compose up -d
155
157
  docker compose ps
156
158
  ```
157
159
 
160
+ To build the image from source instead, run `docker build -t envielxyz/swayrouter:latest .`
161
+ before starting Compose.
162
+
158
163
  Docker also generates persistent secrets in its data volume. Copy
159
164
  `.env.example` to `.env` only when you want explicit deployment overrides.
160
165
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swayrouter",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Self-hosted AI gateway with a clean local dashboard",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -58,22 +58,45 @@ export function getDownloadStatus() {
58
58
  return { downloading: dlState.downloading, progress: dlState.progress };
59
59
  }
60
60
 
61
+ function wait(ms) {
62
+ return new Promise((resolve) => setTimeout(resolve, ms));
63
+ }
64
+
65
+ function removeFile(filePath) {
66
+ try {
67
+ fs.unlinkSync(filePath);
68
+ } catch (error) {
69
+ if (error?.code !== "ENOENT") throw error;
70
+ }
71
+ }
72
+
61
73
  function downloadFile(url, dest) {
62
74
  return new Promise((resolve, reject) => {
63
75
  const file = fs.createWriteStream(dest);
76
+ let settled = false;
77
+
78
+ const fail = (error) => {
79
+ if (settled) return;
80
+ settled = true;
81
+ dlState.downloading = false;
82
+ dlState.progress = 0;
83
+ file.destroy();
84
+ try { removeFile(dest); } catch {}
85
+ reject(error);
86
+ };
64
87
 
65
88
  https.get(url, (response) => {
66
89
  if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
67
- file.close();
68
- fs.unlinkSync(dest);
69
- downloadFile(response.headers.location, dest).then(resolve).catch(reject);
90
+ response.resume();
91
+ file.destroy();
92
+ try { removeFile(dest); } catch (error) { fail(error); return; }
93
+ downloadFile(response.headers.location, dest).then(resolve).catch(fail);
70
94
  return;
71
95
  }
72
96
 
73
97
  if (response.statusCode !== 200) {
74
- file.close();
75
- fs.unlinkSync(dest);
76
- reject(new Error(`Download failed with status ${response.statusCode}`));
98
+ response.resume();
99
+ fail(new Error(`Download failed with status ${response.statusCode}`));
77
100
  return;
78
101
  }
79
102
 
@@ -90,25 +113,18 @@ function downloadFile(url, dest) {
90
113
  response.pipe(file);
91
114
 
92
115
  file.on("finish", () => {
93
- dlState.downloading = false;
94
- dlState.progress = 100;
95
- file.close(() => resolve(dest));
116
+ file.once("close", () => {
117
+ if (settled) return;
118
+ settled = true;
119
+ dlState.downloading = false;
120
+ dlState.progress = 100;
121
+ resolve(dest);
122
+ });
96
123
  });
97
124
 
98
- file.on("error", (err) => {
99
- dlState.downloading = false;
100
- dlState.progress = 0;
101
- file.close();
102
- fs.unlinkSync(dest);
103
- reject(err);
104
- });
105
- }).on("error", (err) => {
106
- dlState.downloading = false;
107
- dlState.progress = 0;
108
- file.close();
109
- if (fs.existsSync(dest)) fs.unlinkSync(dest);
110
- reject(err);
111
- });
125
+ file.on("error", fail);
126
+ response.on("error", fail);
127
+ }).on("error", fail);
112
128
  });
113
129
  }
114
130
 
@@ -131,6 +147,38 @@ function isValidBinary(filePath) {
131
147
  }
132
148
  }
133
149
 
150
+ async function installDownloadedBinary(downloadPath) {
151
+ if (!isValidBinary(downloadPath)) {
152
+ throw new Error("Downloaded cloudflared binary is invalid");
153
+ }
154
+
155
+ let lastError = null;
156
+ for (let attempt = 0; attempt < 6; attempt++) {
157
+ try {
158
+ if (fs.existsSync(BIN_PATH)) removeFile(BIN_PATH);
159
+ fs.renameSync(downloadPath, BIN_PATH);
160
+ if (!isValidBinary(BIN_PATH)) throw new Error("Installed cloudflared binary is invalid");
161
+ return;
162
+ } catch (error) {
163
+ lastError = error;
164
+ if (IS_WINDOWS && fs.existsSync(downloadPath)) {
165
+ try {
166
+ fs.copyFileSync(downloadPath, BIN_PATH);
167
+ if (isValidBinary(BIN_PATH)) {
168
+ try { removeFile(downloadPath); } catch {}
169
+ return;
170
+ }
171
+ } catch (copyError) {
172
+ lastError = copyError;
173
+ }
174
+ }
175
+ await wait(150 * (attempt + 1));
176
+ }
177
+ }
178
+
179
+ throw new Error(`Unable to install cloudflared binary: ${lastError?.message || "unknown file error"}`);
180
+ }
181
+
134
182
  let downloadPromise = null;
135
183
 
136
184
  export async function ensureCloudflared() {
@@ -146,7 +194,9 @@ async function _ensureCloudflared() {
146
194
 
147
195
  const tmpPath = `${BIN_PATH}.tmp`;
148
196
  if (fs.existsSync(tmpPath)) {
149
- try { fs.unlinkSync(tmpPath); } catch { }
197
+ try { removeFile(tmpPath); } catch (error) {
198
+ throw new Error(`Unable to clear stale cloudflared download: ${error.message}`);
199
+ }
150
200
  }
151
201
 
152
202
  if (fs.existsSync(BIN_PATH)) {
@@ -165,11 +215,16 @@ async function _ensureCloudflared() {
165
215
 
166
216
  await downloadFile(url, downloadDest);
167
217
 
168
- if (isArchive) {
169
- execSync(`tar -xzf "${downloadDest}" -C "${BIN_DIR}"`, { stdio: "pipe", windowsHide: true });
170
- fs.unlinkSync(downloadDest);
171
- } else {
172
- fs.renameSync(downloadDest, BIN_PATH);
218
+ try {
219
+ if (isArchive) {
220
+ execSync(`tar -xzf "${downloadDest}" -C "${BIN_DIR}"`, { stdio: "pipe", windowsHide: true });
221
+ removeFile(downloadDest);
222
+ } else {
223
+ await installDownloadedBinary(downloadDest);
224
+ }
225
+ } catch (error) {
226
+ try { removeFile(downloadDest); } catch {}
227
+ throw error;
173
228
  }
174
229
 
175
230
  if (!IS_WINDOWS) {