forge-remote 0.1.4 → 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 +14 -50
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,20 +427,17 @@ 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
|
+
// Primary method: REST API with retry for newly created apps.
|
|
434
435
|
const maxSdkRetries = 4;
|
|
435
436
|
const retryDelayMs = 5000; // 5 seconds between retries.
|
|
436
437
|
|
|
437
438
|
for (let attempt = 1; attempt <= maxSdkRetries; attempt++) {
|
|
438
439
|
try {
|
|
439
|
-
|
|
440
|
-
["firebase", "apps:sdkconfig", "web", appId, "--project", projectId],
|
|
441
|
-
"Failed to get SDK config.",
|
|
442
|
-
);
|
|
440
|
+
sdkConfig = await getWebAppConfig(projectId, appId);
|
|
443
441
|
break; // Success — exit retry loop.
|
|
444
442
|
} catch (e) {
|
|
445
443
|
if (attempt < maxSdkRetries) {
|
|
@@ -451,23 +449,23 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
|
|
|
451
449
|
);
|
|
452
450
|
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
453
451
|
} else {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
chalk.bold(
|
|
458
|
-
" This usually means the Firebase web app hasn't fully propagated yet.\n",
|
|
459
|
-
) +
|
|
452
|
+
throw new Error(
|
|
453
|
+
`Could not retrieve SDK config after ${maxSdkRetries} attempts.\n` +
|
|
454
|
+
` Last error: ${e.message}\n\n` +
|
|
460
455
|
chalk.bold(" Try these steps:\n\n") +
|
|
461
456
|
chalk.cyan(
|
|
462
457
|
` 1. Wait 30 seconds, then re-run: forge-remote init\n`,
|
|
463
458
|
) +
|
|
464
|
-
chalk.cyan(
|
|
459
|
+
chalk.cyan(
|
|
460
|
+
` 2. Or get the config from the Firebase Console:\n`,
|
|
461
|
+
) +
|
|
465
462
|
chalk.dim(
|
|
466
|
-
` firebase
|
|
463
|
+
` https://console.firebase.google.com/project/${projectId}/settings/general\n`,
|
|
467
464
|
) +
|
|
468
465
|
chalk.dim(
|
|
469
|
-
`
|
|
466
|
+
` Scroll to "Your apps" → Web app → Config snippet\n`,
|
|
470
467
|
) +
|
|
468
|
+
chalk.dim(` Save it to: ~/.forge-remote/sdk-config.json\n`) +
|
|
471
469
|
chalk.dim(
|
|
472
470
|
` Format: { "apiKey": "...", "authDomain": "...", "projectId": "...",\n`,
|
|
473
471
|
) +
|
|
@@ -475,46 +473,12 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
|
|
|
475
473
|
` "storageBucket": "...", "messagingSenderId": "...", "appId": "..." }\n`,
|
|
476
474
|
),
|
|
477
475
|
);
|
|
478
|
-
throw fallbackErr;
|
|
479
476
|
}
|
|
480
477
|
}
|
|
481
478
|
}
|
|
482
479
|
|
|
483
|
-
// Parse the config — output may be JSON or JS object.
|
|
484
|
-
const jsonMatch = sdkOutput.match(/\{[\s\S]*\}/);
|
|
485
|
-
if (jsonMatch) {
|
|
486
|
-
try {
|
|
487
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
488
|
-
sdkConfig = {
|
|
489
|
-
apiKey: parsed.apiKey || null,
|
|
490
|
-
authDomain: parsed.authDomain || null,
|
|
491
|
-
projectId: parsed.projectId || null,
|
|
492
|
-
storageBucket: parsed.storageBucket || null,
|
|
493
|
-
messagingSenderId: parsed.messagingSenderId || null,
|
|
494
|
-
appId: parsed.appId || null,
|
|
495
|
-
};
|
|
496
|
-
} catch {
|
|
497
|
-
// Not valid JSON — fall back to regex.
|
|
498
|
-
const parseField = (field) => {
|
|
499
|
-
const regex = new RegExp(`"?${field}"?:\\s*"([^"]+)"`);
|
|
500
|
-
const match = sdkOutput.match(regex);
|
|
501
|
-
return match ? match[1] : null;
|
|
502
|
-
};
|
|
503
|
-
sdkConfig = {
|
|
504
|
-
apiKey: parseField("apiKey"),
|
|
505
|
-
authDomain: parseField("authDomain"),
|
|
506
|
-
projectId: parseField("projectId"),
|
|
507
|
-
storageBucket: parseField("storageBucket"),
|
|
508
|
-
messagingSenderId: parseField("messagingSenderId"),
|
|
509
|
-
appId: parseField("appId"),
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
480
|
if (!sdkConfig.apiKey || !sdkConfig.projectId) {
|
|
515
|
-
throw new Error(
|
|
516
|
-
"Could not parse apiKey or projectId from SDK config output.",
|
|
517
|
-
);
|
|
481
|
+
throw new Error("SDK config is missing apiKey or projectId.");
|
|
518
482
|
}
|
|
519
483
|
|
|
520
484
|
console.log(chalk.green(" ✓ SDK config retrieved"));
|