forge-remote 0.1.3 → 0.1.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/package.json +1 -1
- package/src/google-auth.js +41 -0
- package/src/init.js +44 -35
- package/src/session-manager.js +8 -3
package/package.json
CHANGED
package/src/google-auth.js
CHANGED
|
@@ -80,6 +80,47 @@ export async function getAccessToken() {
|
|
|
80
80
|
return data.access_token;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// ─── Web App SDK Config ─────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fetch the Firebase web app SDK config via REST API.
|
|
87
|
+
* This bypasses the `firebase apps:sdkconfig` CLI command, which sometimes
|
|
88
|
+
* returns 403 on newly created projects.
|
|
89
|
+
*
|
|
90
|
+
* Returns an object with apiKey, authDomain, projectId, storageBucket,
|
|
91
|
+
* messagingSenderId, and appId — or throws on failure.
|
|
92
|
+
*/
|
|
93
|
+
export async function getWebAppConfig(projectId, appId) {
|
|
94
|
+
const token = await getAccessToken();
|
|
95
|
+
|
|
96
|
+
const response = await fetch(
|
|
97
|
+
`https://firebase.googleapis.com/v1beta1/projects/${projectId}/webApps/${appId}/config`,
|
|
98
|
+
{
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${token}`,
|
|
101
|
+
"X-Goog-User-Project": projectId,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const err = await response.json().catch(() => ({}));
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Failed to fetch web app config: ${err.error?.message || response.statusText}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
return {
|
|
115
|
+
apiKey: data.apiKey || null,
|
|
116
|
+
authDomain: data.authDomain || null,
|
|
117
|
+
projectId: data.projectId || null,
|
|
118
|
+
storageBucket: data.storageBucket || null,
|
|
119
|
+
messagingSenderId: data.messagingSenderId || null,
|
|
120
|
+
appId: data.appId || null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
83
124
|
// ─── Anonymous Authentication ───────────────────────────────────────────────
|
|
84
125
|
|
|
85
126
|
/**
|
package/src/init.js
CHANGED
|
@@ -15,6 +15,7 @@ import chalk from "chalk";
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
readRefreshToken,
|
|
18
|
+
getWebAppConfig,
|
|
18
19
|
enableAnonymousAuth,
|
|
19
20
|
enableApi,
|
|
20
21
|
findFirebaseAdminSdkAccount,
|
|
@@ -426,50 +427,58 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
|
|
|
426
427
|
|
|
427
428
|
console.log(chalk.green(` ✓ App ID: ${appId}`));
|
|
428
429
|
|
|
429
|
-
// Get SDK config.
|
|
430
|
+
// Get SDK config via REST API (bypasses flaky `firebase apps:sdkconfig` CLI).
|
|
430
431
|
let sdkConfig = {};
|
|
431
432
|
|
|
432
433
|
try {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
);
|
|
434
|
+
// Primary method: REST API with retry for newly created apps.
|
|
435
|
+
const maxSdkRetries = 4;
|
|
436
|
+
const retryDelayMs = 5000; // 5 seconds between retries.
|
|
437
437
|
|
|
438
|
-
|
|
439
|
-
const jsonMatch = sdkOutput.match(/\{[\s\S]*\}/);
|
|
440
|
-
if (jsonMatch) {
|
|
438
|
+
for (let attempt = 1; attempt <= maxSdkRetries; attempt++) {
|
|
441
439
|
try {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
440
|
+
sdkConfig = await getWebAppConfig(projectId, appId);
|
|
441
|
+
break; // Success — exit retry loop.
|
|
442
|
+
} catch (e) {
|
|
443
|
+
if (attempt < maxSdkRetries) {
|
|
444
|
+
console.log(
|
|
445
|
+
chalk.yellow(
|
|
446
|
+
` ⚠ SDK config not available yet (attempt ${attempt}/${maxSdkRetries}). ` +
|
|
447
|
+
`Retrying in ${retryDelayMs / 1000}s...`,
|
|
448
|
+
),
|
|
449
|
+
);
|
|
450
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
451
|
+
} else {
|
|
452
|
+
throw new Error(
|
|
453
|
+
`Could not retrieve SDK config after ${maxSdkRetries} attempts.\n` +
|
|
454
|
+
` Last error: ${e.message}\n\n` +
|
|
455
|
+
chalk.bold(" Try these steps:\n\n") +
|
|
456
|
+
chalk.cyan(
|
|
457
|
+
` 1. Wait 30 seconds, then re-run: forge-remote init\n`,
|
|
458
|
+
) +
|
|
459
|
+
chalk.cyan(
|
|
460
|
+
` 2. Or get the config from the Firebase Console:\n`,
|
|
461
|
+
) +
|
|
462
|
+
chalk.dim(
|
|
463
|
+
` https://console.firebase.google.com/project/${projectId}/settings/general\n`,
|
|
464
|
+
) +
|
|
465
|
+
chalk.dim(
|
|
466
|
+
` Scroll to "Your apps" → Web app → Config snippet\n`,
|
|
467
|
+
) +
|
|
468
|
+
chalk.dim(` Save it to: ~/.forge-remote/sdk-config.json\n`) +
|
|
469
|
+
chalk.dim(
|
|
470
|
+
` Format: { "apiKey": "...", "authDomain": "...", "projectId": "...",\n`,
|
|
471
|
+
) +
|
|
472
|
+
chalk.dim(
|
|
473
|
+
` "storageBucket": "...", "messagingSenderId": "...", "appId": "..." }\n`,
|
|
474
|
+
),
|
|
475
|
+
);
|
|
476
|
+
}
|
|
466
477
|
}
|
|
467
478
|
}
|
|
468
479
|
|
|
469
480
|
if (!sdkConfig.apiKey || !sdkConfig.projectId) {
|
|
470
|
-
throw new Error(
|
|
471
|
-
"Could not parse apiKey or projectId from SDK config output.",
|
|
472
|
-
);
|
|
481
|
+
throw new Error("SDK config is missing apiKey or projectId.");
|
|
473
482
|
}
|
|
474
483
|
|
|
475
484
|
console.log(chalk.green(" ✓ SDK config retrieved"));
|
package/src/session-manager.js
CHANGED
|
@@ -279,9 +279,14 @@ async function handleDesktopCommand(desktopId, commandDoc) {
|
|
|
279
279
|
|
|
280
280
|
try {
|
|
281
281
|
switch (data.type) {
|
|
282
|
-
case "start_session":
|
|
283
|
-
await startNewSession(desktopId, data.payload);
|
|
284
|
-
|
|
282
|
+
case "start_session": {
|
|
283
|
+
const sessionId = await startNewSession(desktopId, data.payload);
|
|
284
|
+
await cmdRef.update({
|
|
285
|
+
status: "completed",
|
|
286
|
+
result: { sessionId },
|
|
287
|
+
});
|
|
288
|
+
return; // Skip generic completed update below
|
|
289
|
+
}
|
|
285
290
|
case "rescan_projects": {
|
|
286
291
|
const { scanProjects } = await import("./project-scanner.js");
|
|
287
292
|
const { updateProjects } = await import("./desktop.js");
|