@serverkgg/bridge 0.1.7 → 0.1.8

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.
@@ -16,6 +16,8 @@ const STEAM_CLIENT_DIRECTORY = `${STEAM_DIRECTORY}/sdk64`;
16
16
 
17
17
  const SENTRY_FILE = ".sentry.b64";
18
18
 
19
+ const APP_INFO_FILE = "appinfo.vdf";
20
+
19
21
  const UPDATE_TIMEOUT_MS = 3_600_000;
20
22
 
21
23
  const BOOTSTRAP_TIMEOUT_MS = 300_000;
@@ -26,8 +28,43 @@ const ATTEMPTS = 3;
26
28
 
27
29
  const RETRY_DELAY_MS = 5000;
28
30
 
31
+ const STATE_CODE = /\b0x[0-9a-f]+\b/gi;
32
+
33
+ const DISK_FULL_STATES = [
34
+ "0x202",
35
+ "0x212",
36
+ ];
37
+
38
+ const TRANSIENT_STATES = [
39
+ "0x402",
40
+ "0x602",
41
+ "0x606",
42
+ "0x626",
43
+ "0x6a6",
44
+ ];
45
+
29
46
  const NO_SUBSCRIPTION = "No subscription";
30
47
 
48
+ const BRANCH_PASSWORD = "Password check for AppId";
49
+
50
+ const INVALID_PLATFORM = /invalid platform/i;
51
+
52
+ const MISSING_CONFIGURATION = /missing configuration/i;
53
+
54
+ export enum SteamcmdFailure {
55
+ DiskFull = "disk-full",
56
+ InvalidPlatform = "invalid-platform",
57
+ NoSubscription = "no-subscription",
58
+ BranchPassword = "branch-password",
59
+ Transient = "transient",
60
+ Unknown = "unknown",
61
+ }
62
+
63
+ const RETRYABLE_FAILURES = new Set([
64
+ SteamcmdFailure.Transient,
65
+ SteamcmdFailure.Unknown,
66
+ ]);
67
+
31
68
  export interface SteamcmdOptions {
32
69
  appId: string;
33
70
  label: string;
@@ -80,6 +117,8 @@ export const steamcmdArgv = (options: SteamcmdOptions, root: string, args: strin
80
117
 
81
118
  export const steamcmdUpdateArgs = (appId: string, update: SteamcmdUpdateOptions) => {
82
119
  const args = [
120
+ "+app_info_update",
121
+ "1",
83
122
  "+app_update",
84
123
  appId,
85
124
  ];
@@ -103,18 +142,67 @@ export const steamcmdUpdateArgs = (appId: string, update: SteamcmdUpdateOptions)
103
142
  return args;
104
143
  };
105
144
 
106
- export const steamcmdFailureText = (result: BridgeExecResult, label: string): LocalizedText => {
107
- if (`${result.stdout}\n${result.stderr}`.includes(NO_SUBSCRIPTION)) {
108
- return {
109
- ar: `رفض Steam تنزيل ${label} لأن حساب التنزيل لا يملكه. تواصل مع الدعم.`,
110
- en: `Steam refused to download ${label} because the download account doesn't own it. Contact support.`,
111
- };
145
+ export const steamcmdFailureOf = (result: BridgeExecResult): SteamcmdFailure => {
146
+ const output = `${result.stdout}\n${result.stderr}`;
147
+ const states = new Set(Array.from(output.matchAll(STATE_CODE), (match) => match[0].toLowerCase()));
148
+
149
+ if (DISK_FULL_STATES.some((state) => states.has(state))) {
150
+ return SteamcmdFailure.DiskFull;
112
151
  }
113
152
 
114
- return {
115
- ar: `تعذّر تنزيل ${label} من Steam، جرب مرة ثانية.`,
116
- en: `We couldn't download ${label} from Steam. Try again.`,
117
- };
153
+ if (INVALID_PLATFORM.test(output)) {
154
+ return SteamcmdFailure.InvalidPlatform;
155
+ }
156
+
157
+ if (output.includes(NO_SUBSCRIPTION)) {
158
+ return SteamcmdFailure.NoSubscription;
159
+ }
160
+
161
+ if (output.includes(BRANCH_PASSWORD)) {
162
+ return SteamcmdFailure.BranchPassword;
163
+ }
164
+
165
+ if (TRANSIENT_STATES.some((state) => states.has(state)) || MISSING_CONFIGURATION.test(output)) {
166
+ return SteamcmdFailure.Transient;
167
+ }
168
+
169
+ return SteamcmdFailure.Unknown;
170
+ };
171
+
172
+ export const steamcmdRetryable = (failure: SteamcmdFailure) => {
173
+ return RETRYABLE_FAILURES.has(failure);
174
+ };
175
+
176
+ export const steamcmdFailureText = (result: BridgeExecResult, label: string): LocalizedText => {
177
+ switch (steamcmdFailureOf(result)) {
178
+ case SteamcmdFailure.DiskFull: {
179
+ return {
180
+ ar: `امتلأت مساحة سيرفرك، فتعذّر تنزيل ${label} من Steam. احذف الملفات التي لا تحتاجها من صفحة الملفات أو انتقل إلى باقة أكبر، ثم جرب مرة ثانية.`,
181
+ en: `Your server is out of space, so we couldn't download ${label} from Steam. Delete files you don't need on the Files page or move to a larger plan, then try again.`,
182
+ };
183
+ }
184
+
185
+ case SteamcmdFailure.NoSubscription: {
186
+ return {
187
+ ar: `رفض Steam تنزيل ${label} لأن حساب التنزيل لا يملكه. تواصل مع الدعم.`,
188
+ en: `Steam refused to download ${label} because the download account doesn't own it. Contact support.`,
189
+ };
190
+ }
191
+
192
+ case SteamcmdFailure.BranchPassword: {
193
+ return {
194
+ ar: `كلمة مرور الفرع على Steam غير صحيحة، فتعذّر تنزيل ${label}. صحّحها من الإعدادات.`,
195
+ en: `The Steam branch password is wrong, so we couldn't download ${label}. Fix it in the settings.`,
196
+ };
197
+ }
198
+
199
+ default: {
200
+ return {
201
+ ar: `تعذّر تنزيل ${label} من Steam، جرب مرة ثانية.`,
202
+ en: `We couldn't download ${label} from Steam. Try again.`,
203
+ };
204
+ }
205
+ }
118
206
  };
119
207
 
120
208
  const installRoot = async (context: BridgeContext) => {
@@ -197,15 +285,20 @@ export const createSteamcmd = (context: BridgeContext, options: SteamcmdOptions)
197
285
 
198
286
  failure = result;
199
287
 
200
- if (attempt < attempts) {
201
- context.log("steamcmd did not finish, trying again", {
202
- attempt,
203
- of: attempts,
204
- code: result.code,
205
- });
288
+ const kind = steamcmdFailureOf(result);
206
289
 
207
- await Bun.sleep(RETRY_DELAY_MS);
290
+ if (attempt === attempts || !steamcmdRetryable(kind)) {
291
+ break;
208
292
  }
293
+
294
+ context.log("steamcmd did not finish, trying again", {
295
+ attempt,
296
+ of: attempts,
297
+ code: result.code,
298
+ failure: kind,
299
+ });
300
+
301
+ await Bun.sleep(RETRY_DELAY_MS);
209
302
  }
210
303
 
211
304
  if (!failure) {
@@ -213,10 +306,45 @@ export const createSteamcmd = (context: BridgeContext, options: SteamcmdOptions)
213
306
  }
214
307
 
215
308
  throw new BridgeUserError(steamcmdFailureText(failure, options.label), {
216
- cause: new Error(`steamcmd exited with code ${failure.code} — ${execDetail(failure)}`),
309
+ cause: new Error(
310
+ `steamcmd exited with code ${failure.code} (${steamcmdFailureOf(failure)}) — ${execDetail(failure)}`,
311
+ ),
217
312
  });
218
313
  };
219
314
 
315
+ const clearAppInfo = async () => {
316
+ const roots: string[] = [];
317
+
318
+ for (const path of [
319
+ directory,
320
+ STEAM_DIRECTORY,
321
+ ]) {
322
+ if (await context.files.exists(path)) {
323
+ roots.push(path);
324
+ }
325
+ }
326
+
327
+ if (roots.length === 0) {
328
+ return;
329
+ }
330
+
331
+ const result = await context.exec([
332
+ "find",
333
+ ...roots,
334
+ "-type",
335
+ "f",
336
+ "-name",
337
+ APP_INFO_FILE,
338
+ "-delete",
339
+ ]);
340
+
341
+ if (result.code !== 0) {
342
+ context.log.warn("the cached steam app info could not be cleared, updating anyway", {
343
+ code: result.code,
344
+ });
345
+ }
346
+ };
347
+
220
348
  return {
221
349
  directory,
222
350
  script,
@@ -271,7 +399,15 @@ export const createSteamcmd = (context: BridgeContext, options: SteamcmdOptions)
271
399
  run,
272
400
 
273
401
  async update(update) {
402
+ await clearAppInfo();
274
403
  await run(steamcmdUpdateArgs(options.appId, update));
404
+
405
+ const build = await installedBuildId(context, options.appId);
406
+
407
+ context.log("steamcmd finished the update", {
408
+ app: options.appId,
409
+ build,
410
+ });
275
411
  },
276
412
 
277
413
  async buildId() {