@omega.js/manager 0.1.0
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/.claude-plugin/marketplace.json +14 -0
- package/LICENSE +98 -0
- package/README.md +165 -0
- package/bin/omega +2 -0
- package/bin/omg +2 -0
- package/claude-plugin/.claude-plugin/plugin.json +11 -0
- package/claude-plugin/.mcp.json +9 -0
- package/claude-plugin/README.md +139 -0
- package/claude-plugin/hooks/gate/mark.sh +58 -0
- package/claude-plugin/hooks/gate/run.sh +97 -0
- package/claude-plugin/hooks/guard/run.sh +249 -0
- package/claude-plugin/hooks/hooks.json +63 -0
- package/claude-plugin/hooks/inject/run.sh +127 -0
- package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
- package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
- package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
- package/claude-plugin/hooks/quality/run.sh +132 -0
- package/claude-plugin/hooks/shape/run.sh +45 -0
- package/claude-plugin/mcp-router-launch.js +29 -0
- package/claude-plugin/skills/README.md +48 -0
- package/claude-plugin/skills/accessibility/SKILL.md +31 -0
- package/claude-plugin/skills/analytics/SKILL.md +30 -0
- package/claude-plugin/skills/backend/SKILL.md +25 -0
- package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
- package/claude-plugin/skills/browser/SKILL.md +64 -0
- package/claude-plugin/skills/client/SKILL.md +24 -0
- package/claude-plugin/skills/desktop/SKILL.md +25 -0
- package/claude-plugin/skills/extension/SKILL.md +25 -0
- package/claude-plugin/skills/main/SKILL.md +54 -0
- package/claude-plugin/skills/manager/SKILL.md +25 -0
- package/claude-plugin/skills/seo/SKILL.md +36 -0
- package/claude-plugin/skills/theme/SKILL.md +33 -0
- package/claude-plugin/skills/web/SKILL.md +29 -0
- package/dist/cli-run.js +34 -0
- package/dist/cli.js +29 -0
- package/dist/commands/build.js +17 -0
- package/dist/commands/clean.js +16 -0
- package/dist/commands/company.js +46 -0
- package/dist/commands/deploy.js +143 -0
- package/dist/commands/dev.js +437 -0
- package/dist/commands/devlog.js +25 -0
- package/dist/commands/help.js +48 -0
- package/dist/commands/manage.js +53 -0
- package/dist/commands/migrate.js +76 -0
- package/dist/commands/onboard.js +31 -0
- package/dist/commands/pipeline.js +319 -0
- package/dist/commands/test.js +380 -0
- package/dist/commands/update.js +128 -0
- package/dist/commands/version.js +8 -0
- package/dist/company-init.js +167 -0
- package/dist/company.js +283 -0
- package/dist/config.js +1359 -0
- package/dist/devlog/index.js +226 -0
- package/dist/devlog/lib/collect-commits.js +136 -0
- package/dist/devlog/lib/generate-post.js +201 -0
- package/dist/devlog/lib/ghostii.js +122 -0
- package/dist/devlog/lib/project-map.js +98 -0
- package/dist/devlog/lib/publish-website.js +88 -0
- package/dist/index.js +45 -0
- package/dist/lib/agents-md.js +201 -0
- package/dist/lib/analytics-secret.js +22 -0
- package/dist/lib/argv.js +21 -0
- package/dist/lib/auth-admin.js +120 -0
- package/dist/lib/automation-client.js +210 -0
- package/dist/lib/backend-marketing.js +49 -0
- package/dist/lib/brand.js +240 -0
- package/dist/lib/bundle-id.js +53 -0
- package/dist/lib/claude-settings.js +125 -0
- package/dist/lib/company-scaffold.js +213 -0
- package/dist/lib/company.js +227 -0
- package/dist/lib/config-flow.js +350 -0
- package/dist/lib/config-write.js +41 -0
- package/dist/lib/custom-target.js +75 -0
- package/dist/lib/domain-utils.js +19 -0
- package/dist/lib/duration.js +39 -0
- package/dist/lib/env-order.js +236 -0
- package/dist/lib/env-secret.js +78 -0
- package/dist/lib/firestore-rest.js +326 -0
- package/dist/lib/framework-bin.js +156 -0
- package/dist/lib/gitignore.js +63 -0
- package/dist/lib/google-auth.js +495 -0
- package/dist/lib/google-token.js +66 -0
- package/dist/lib/jwt.js +31 -0
- package/dist/lib/legacy-oauth.js +59 -0
- package/dist/lib/node-version.js +127 -0
- package/dist/lib/owner-plan.js +78 -0
- package/dist/lib/package-scripts.js +152 -0
- package/dist/lib/preflight.js +470 -0
- package/dist/lib/product-create.js +144 -0
- package/dist/lib/run-command.js +74 -0
- package/dist/lib/run-gates.js +70 -0
- package/dist/lib/run-output.js +42 -0
- package/dist/lib/run-summary.js +384 -0
- package/dist/lib/scaffold.js +427 -0
- package/dist/lib/service-input.js +207 -0
- package/dist/lib/service-runner.js +466 -0
- package/dist/lib/stale.js +24 -0
- package/dist/lib/target-selection.js +155 -0
- package/dist/lib/verb-fanout.js +128 -0
- package/dist/lib/verify-live.js +188 -0
- package/dist/manage.js +311 -0
- package/dist/omega-bin.js +7 -0
- package/dist/onboard.js +532 -0
- package/dist/services/account/ensure/users.js +259 -0
- package/dist/services/account/index.js +97 -0
- package/dist/services/account/lib/backend-client.js +107 -0
- package/dist/services/account/lib/password.js +36 -0
- package/dist/services/account/lib/resolve-password.js +100 -0
- package/dist/services/advertising/ensure/sites.js +101 -0
- package/dist/services/advertising/index.js +101 -0
- package/dist/services/advertising/lib/adsense-api.js +41 -0
- package/dist/services/ai/ensure/keys.js +21 -0
- package/dist/services/ai/index.js +36 -0
- package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
- package/dist/services/analytics/ensure/google-streams.js +256 -0
- package/dist/services/analytics/ensure/meta-pixel.js +22 -0
- package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
- package/dist/services/analytics/index.js +134 -0
- package/dist/services/analytics/lib/analytics-api.js +156 -0
- package/dist/services/analytics/lib/meta-api.js +83 -0
- package/dist/services/analytics/lib/pixel-account.js +78 -0
- package/dist/services/analytics/lib/pixel-provision.js +152 -0
- package/dist/services/analytics/lib/pixel-specs.js +57 -0
- package/dist/services/analytics/lib/pixel-token.js +88 -0
- package/dist/services/analytics/lib/property-flow.js +77 -0
- package/dist/services/analytics/lib/tiktok-api.js +157 -0
- package/dist/services/analytics/lib/tiktok-auth.js +153 -0
- package/dist/services/assets/ensure/logo-gen.js +68 -0
- package/dist/services/assets/index.js +109 -0
- package/dist/services/assets/lib/assets-config.js +168 -0
- package/dist/services/assets/lib/brandmark-api.js +160 -0
- package/dist/services/assets/lib/derived.js +113 -0
- package/dist/services/assets/lib/font-loader.js +67 -0
- package/dist/services/assets/lib/reconcile.js +122 -0
- package/dist/services/assets/lib/reset.js +118 -0
- package/dist/services/assets/lib/svg-logo-generator.js +154 -0
- package/dist/services/assets/lib/svg-to-black.js +32 -0
- package/dist/services/assets/write/favicons.js +82 -0
- package/dist/services/assets/write/icons.js +64 -0
- package/dist/services/assets/write/process.js +96 -0
- package/dist/services/assets/write/reconcile.js +30 -0
- package/dist/services/assets/write/social-icons.js +108 -0
- package/dist/services/assets/write/templates.js +345 -0
- package/dist/services/bookmark/ensure/sync.js +255 -0
- package/dist/services/bookmark/index.js +17 -0
- package/dist/services/campaigns/ensure/contact-person.js +40 -0
- package/dist/services/campaigns/ensure/custom-fields.js +84 -0
- package/dist/services/campaigns/ensure/domain-auth.js +92 -0
- package/dist/services/campaigns/ensure/event-webhook.js +89 -0
- package/dist/services/campaigns/ensure/link-branding.js +187 -0
- package/dist/services/campaigns/ensure/list.js +50 -0
- package/dist/services/campaigns/ensure/segments.js +109 -0
- package/dist/services/campaigns/ensure/sender-identity.js +153 -0
- package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
- package/dist/services/campaigns/index.js +79 -0
- package/dist/services/campaigns/lib/dns-sync.js +81 -0
- package/dist/services/campaigns/lib/segment-query.js +138 -0
- package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
- package/dist/services/captcha/ensure/site-key.js +83 -0
- package/dist/services/captcha/index.js +78 -0
- package/dist/services/captcha/lib/console-url.js +45 -0
- package/dist/services/captcha/lib/recaptcha-api.js +36 -0
- package/dist/services/certificates/ensure/api-key.js +22 -0
- package/dist/services/certificates/ensure/bundle-ids.js +115 -0
- package/dist/services/certificates/ensure/certificates.js +187 -0
- package/dist/services/certificates/ensure/profiles.js +143 -0
- package/dist/services/certificates/index.js +225 -0
- package/dist/services/certificates/lib/apple-api.js +183 -0
- package/dist/services/certificates/lib/certificate-manager.js +252 -0
- package/dist/services/certificates/lib/identifier-manager.js +107 -0
- package/dist/services/certificates/lib/keychain.js +135 -0
- package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
- package/dist/services/certificates/lib/profile-manager.js +115 -0
- package/dist/services/chat/data/baseline-knowledge.md +96 -0
- package/dist/services/chat/ensure/chat.js +106 -0
- package/dist/services/chat/ensure/user.js +45 -0
- package/dist/services/chat/index.js +139 -0
- package/dist/services/chat/lib/baseline-knowledge.js +137 -0
- package/dist/services/cloud/ensure/authentication.js +267 -0
- package/dist/services/cloud/ensure/billing.js +120 -0
- package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
- package/dist/services/cloud/ensure/database.js +60 -0
- package/dist/services/cloud/ensure/firestore.js +79 -0
- package/dist/services/cloud/ensure/functions.js +49 -0
- package/dist/services/cloud/ensure/hosting.js +398 -0
- package/dist/services/cloud/ensure/oauth-consent.js +248 -0
- package/dist/services/cloud/ensure/project-settings.js +72 -0
- package/dist/services/cloud/ensure/sdk-config.js +95 -0
- package/dist/services/cloud/ensure/service-account.js +152 -0
- package/dist/services/cloud/ensure/services.js +103 -0
- package/dist/services/cloud/ensure/storage.js +47 -0
- package/dist/services/cloud/index.js +130 -0
- package/dist/services/cloud/lib/access-heal.js +130 -0
- package/dist/services/cloud/lib/firebase-api.js +662 -0
- package/dist/services/cloud/lib/project-flow.js +102 -0
- package/dist/services/directory/ensure/entry.js +48 -0
- package/dist/services/directory/index.js +64 -0
- package/dist/services/directory/lib/blocks.js +104 -0
- package/dist/services/disperse/index.js +45 -0
- package/dist/services/disperse/write/certs.js +117 -0
- package/dist/services/domain/ensure/nameservers.js +179 -0
- package/dist/services/domain/index.js +69 -0
- package/dist/services/domain/lib/namecheap-api.js +167 -0
- package/dist/services/domain/lib/registrars.js +46 -0
- package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
- package/dist/services/edge/ensure/cache-rules.js +105 -0
- package/dist/services/edge/ensure/dns-records.js +304 -0
- package/dist/services/edge/ensure/email-routing.js +292 -0
- package/dist/services/edge/ensure/rules-configuration.js +98 -0
- package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
- package/dist/services/edge/ensure/rules-redirect.js +107 -0
- package/dist/services/edge/ensure/rules-response-headers.js +90 -0
- package/dist/services/edge/ensure/rules-security.js +142 -0
- package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
- package/dist/services/edge/ensure/workers.js +219 -0
- package/dist/services/edge/ensure/zone-settings.js +108 -0
- package/dist/services/edge/ensure/zone.js +183 -0
- package/dist/services/edge/index.js +73 -0
- package/dist/services/edge/lib/cloudflare-api.js +84 -0
- package/dist/services/edge/lib/dns-records-helpers.js +518 -0
- package/dist/services/edge/lib/read-cache.js +16 -0
- package/dist/services/edge/lib/ruleset-helper.js +108 -0
- package/dist/services/edge/workers/omega-api-proxy.js +43 -0
- package/dist/services/email/data/baseline-filter.md +4 -0
- package/dist/services/email/data/baseline-knowledge.md +86 -0
- package/dist/services/email/ensure/agent.js +107 -0
- package/dist/services/email/ensure/user.js +45 -0
- package/dist/services/email/index.js +138 -0
- package/dist/services/email/lib/baseline.js +82 -0
- package/dist/services/email/lib/knowledge-file.js +53 -0
- package/dist/services/forms/ensure/form.js +46 -0
- package/dist/services/forms/ensure/user.js +45 -0
- package/dist/services/forms/index.js +138 -0
- package/dist/services/migrations/ensure/notifications.js +189 -0
- package/dist/services/migrations/ensure/orders.js +34 -0
- package/dist/services/migrations/ensure/payment-provider.js +132 -0
- package/dist/services/migrations/ensure/payments-intents.js +34 -0
- package/dist/services/migrations/ensure/state-retirement.js +266 -0
- package/dist/services/migrations/ensure/targets-rename.js +124 -0
- package/dist/services/migrations/ensure/users.js +1176 -0
- package/dist/services/migrations/index.js +101 -0
- package/dist/services/migrations/lib/attribution-touch.js +64 -0
- package/dist/services/migrations/lib/ensure-metadata.js +144 -0
- package/dist/services/migrations/lib/migration-runner.js +473 -0
- package/dist/services/migrations/lib/sanitize-strings.js +112 -0
- package/dist/services/migrations/lib/schema-validator.js +154 -0
- package/dist/services/monitoring/ensure/dsn.js +54 -0
- package/dist/services/monitoring/ensure/projects.js +135 -0
- package/dist/services/monitoring/index.js +54 -0
- package/dist/services/monitoring/lib/sentry-api.js +94 -0
- package/dist/services/newsletter/ensure/custom-fields.js +90 -0
- package/dist/services/newsletter/ensure/publication.js +94 -0
- package/dist/services/newsletter/ensure/segments.js +140 -0
- package/dist/services/newsletter/ensure/webhook.js +108 -0
- package/dist/services/newsletter/index.js +64 -0
- package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
- package/dist/services/newsletter/lib/segment-automation.js +577 -0
- package/dist/services/payment/ensure/chargebee-account.js +30 -0
- package/dist/services/payment/ensure/chargebee-products.js +359 -0
- package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
- package/dist/services/payment/ensure/paypal-account.js +36 -0
- package/dist/services/payment/ensure/paypal-products.js +374 -0
- package/dist/services/payment/ensure/paypal-webhook.js +128 -0
- package/dist/services/payment/ensure/stripe-account.js +93 -0
- package/dist/services/payment/ensure/stripe-disputes.js +56 -0
- package/dist/services/payment/ensure/stripe-products.js +245 -0
- package/dist/services/payment/ensure/stripe-radar.js +74 -0
- package/dist/services/payment/ensure/stripe-webhook.js +130 -0
- package/dist/services/payment/index.js +163 -0
- package/dist/services/payment/lib/chargebee-api.js +338 -0
- package/dist/services/payment/lib/payment-utils.js +116 -0
- package/dist/services/payment/lib/paypal-api.js +354 -0
- package/dist/services/payment/lib/provider-setup.js +134 -0
- package/dist/services/payment/lib/stripe-api.js +152 -0
- package/dist/services/repo/ensure/org.js +70 -0
- package/dist/services/repo/ensure/pages.js +72 -0
- package/dist/services/repo/ensure/repo.js +90 -0
- package/dist/services/repo/index.js +52 -0
- package/dist/services/repo/lib/github-api.js +196 -0
- package/dist/services/search/ensure/ga-link.js +57 -0
- package/dist/services/search/ensure/property.js +137 -0
- package/dist/services/search/ensure/sitemaps.js +70 -0
- package/dist/services/search/index.js +61 -0
- package/dist/services/search/lib/search-console-api.js +92 -0
- package/dist/services/seo/ensure/github-repos.js +271 -0
- package/dist/services/seo/index.js +55 -0
- package/dist/services/seo/lib/gh-api.js +161 -0
- package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
- package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
- package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
- package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
- package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
- package/dist/services/seo/templates/index.js +57 -0
- package/dist/services/server/ensure/brands.js +63 -0
- package/dist/services/server/index.js +42 -0
- package/dist/services/testing/ensure/target-checks.js +97 -0
- package/dist/services/testing/index.js +11 -0
- package/dist/services/testing/lib/checks.js +467 -0
- package/dist/services/update/index.js +20 -0
- package/dist/services/update/lib/cache.js +59 -0
- package/dist/services/update/lib/fingerprint.js +157 -0
- package/dist/services/update/write/targets.js +206 -0
- package/dist/services/workspace/ensure/agents.js +42 -0
- package/dist/services/workspace/ensure/claude-settings.js +34 -0
- package/dist/services/workspace/ensure/config.js +54 -0
- package/dist/services/workspace/ensure/defaults.js +60 -0
- package/dist/services/workspace/ensure/env-keys.js +55 -0
- package/dist/services/workspace/ensure/env-order.js +66 -0
- package/dist/services/workspace/ensure/env-rules.js +97 -0
- package/dist/services/workspace/ensure/gitignore.js +18 -0
- package/dist/services/workspace/ensure/scripts.js +126 -0
- package/dist/services/workspace/ensure/structure.js +97 -0
- package/dist/services/workspace/ensure/translation-sdk.js +148 -0
- package/dist/services/workspace/ensure/workflows.js +54 -0
- package/dist/services/workspace/index.js +8 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/certs.js +199 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/flows.js +317 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/prompt.js +187 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/docs/AGENTS.md +200 -0
- package/docs/account.md +45 -0
- package/docs/advertising.md +46 -0
- package/docs/ai.md +34 -0
- package/docs/analytics.md +69 -0
- package/docs/assets.md +57 -0
- package/docs/bookmark.md +26 -0
- package/docs/brand.md +151 -0
- package/docs/campaigns.md +96 -0
- package/docs/captcha.md +46 -0
- package/docs/certificates.md +68 -0
- package/docs/chat.md +47 -0
- package/docs/cloud.md +110 -0
- package/docs/company.md +64 -0
- package/docs/directory.md +140 -0
- package/docs/disperse.md +46 -0
- package/docs/domain.md +56 -0
- package/docs/edge.md +234 -0
- package/docs/email.md +42 -0
- package/docs/forms.md +48 -0
- package/docs/index.md +109 -0
- package/docs/migration.md +203 -0
- package/docs/migrations.md +56 -0
- package/docs/monitoring.md +41 -0
- package/docs/newsletter.md +48 -0
- package/docs/payment.md +83 -0
- package/docs/repo.md +48 -0
- package/docs/search.md +44 -0
- package/docs/seo.md +34 -0
- package/docs/server.md +37 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/testing.md +34 -0
- package/docs/update.md +36 -0
- package/docs/workspace.md +64 -0
- package/package.json +88 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase / Google Cloud API client — omega-manager's FirebaseAPI ported.
|
|
3
|
+
* One authenticated fetch wrapper (Google OAuth2) fronting the Firebase
|
|
4
|
+
* Management, Service Usage, Cloud Resource Manager, IAM, Billing, IAP,
|
|
5
|
+
* Identity Toolkit, Firestore, Realtime Database, Storage, and Hosting APIs.
|
|
6
|
+
*
|
|
7
|
+
* Every call handlers make is a named method here (nothing reaches into the
|
|
8
|
+
* auth layer directly) — tests fake this surface method-for-method.
|
|
9
|
+
*
|
|
10
|
+
* Auth: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET in the brand .env; tokens
|
|
11
|
+
* cache to the brand's .omega/auth/google-tokens.json.
|
|
12
|
+
*/
|
|
13
|
+
const { GoogleOAuth2Client, GOOGLE_SCOPES } = require('../../../lib/google-auth.js');
|
|
14
|
+
|
|
15
|
+
const FIREBASE_API_BASE = 'https://firebase.googleapis.com/v1beta1';
|
|
16
|
+
const SERVICE_USAGE_API_BASE = 'https://serviceusage.googleapis.com/v1';
|
|
17
|
+
const IDENTITY_TOOLKIT_API_BASE = 'https://identitytoolkit.googleapis.com/v2';
|
|
18
|
+
const FIREBASE_HOSTING_API_BASE = 'https://firebasehosting.googleapis.com/v1beta1';
|
|
19
|
+
const RESOURCE_MANAGER_V1 = 'https://cloudresourcemanager.googleapis.com/v1';
|
|
20
|
+
const RESOURCE_MANAGER_V3 = 'https://cloudresourcemanager.googleapis.com/v3';
|
|
21
|
+
const FIRESTORE_API_BASE = 'https://firestore.googleapis.com/v1';
|
|
22
|
+
const CLOUD_BILLING_API_BASE = 'https://cloudbilling.googleapis.com/v1';
|
|
23
|
+
|
|
24
|
+
// Poll budget for a hosting domain claim (see settleDomainClaim)
|
|
25
|
+
const DOMAIN_CLAIM_POLL = { maxAttempts: 3, delayMs: 1000, pendingOk: true };
|
|
26
|
+
|
|
27
|
+
class FirebaseAPI {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
// GOOGLE_SCOPES (the manager-wide union) — one consent covers every
|
|
30
|
+
// Google service; see google-auth.js
|
|
31
|
+
this.auth = new GoogleOAuth2Client({
|
|
32
|
+
clientId: options.clientId || process.env.GOOGLE_CLIENT_ID,
|
|
33
|
+
clientSecret: options.clientSecret || process.env.GOOGLE_CLIENT_SECRET,
|
|
34
|
+
tokenStorePath: options.tokenStorePath || null,
|
|
35
|
+
scopes: GOOGLE_SCOPES,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
request(url, options = {}) {
|
|
40
|
+
return this.auth.makeRequest(url, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Poll a long-running operation until it completes.
|
|
45
|
+
*
|
|
46
|
+
* A spent poll budget is not proof of failure (#56): some Google operations
|
|
47
|
+
* stay RUNNING for as long as the outside world takes to catch up, and a
|
|
48
|
+
* hosting domain claim waiting on DNS verification is the normal case, not
|
|
49
|
+
* an error. Callers whose operation can legitimately outlive the budget
|
|
50
|
+
* pass `pendingOk` and get `{ pending: true, operationName }` back; every
|
|
51
|
+
* other caller keeps the hard timeout. An operation that reports an error
|
|
52
|
+
* is terminal either way.
|
|
53
|
+
*/
|
|
54
|
+
async waitForOperation(operationName, apiBase, options = {}) {
|
|
55
|
+
const maxAttempts = options.maxAttempts || 30;
|
|
56
|
+
const delayMs = options.delayMs || 2000;
|
|
57
|
+
|
|
58
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
59
|
+
const operation = await this.request(`${apiBase}/${operationName}`);
|
|
60
|
+
|
|
61
|
+
if (operation.done) {
|
|
62
|
+
if (operation.error) {
|
|
63
|
+
throw new Error(`Operation failed: ${operation.error.message}`);
|
|
64
|
+
}
|
|
65
|
+
return operation.response;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (options.pendingOk) {
|
|
72
|
+
return { pending: true, operationName };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error('Operation timed out');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ===========================================================================
|
|
79
|
+
// FIREBASE MANAGEMENT — projects + web apps
|
|
80
|
+
// ===========================================================================
|
|
81
|
+
|
|
82
|
+
/** Firebase project details (null if inaccessible) */
|
|
83
|
+
async getProject(projectId) {
|
|
84
|
+
try {
|
|
85
|
+
return await this.request(`${FIREBASE_API_BASE}/projects/${projectId}`);
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Raw project-access probe — getProject() swallows failures into null
|
|
93
|
+
* (its consumers treat "inaccessible" and "absent" alike), but the access
|
|
94
|
+
* self-heal needs the REAL failure: PERMISSION_DENIED here is the
|
|
95
|
+
* identity seam (lib/access-heal.js); anything else is a genuine fault.
|
|
96
|
+
* Resource Manager GET works for any role and for projects not yet
|
|
97
|
+
* Firebase-added.
|
|
98
|
+
*/
|
|
99
|
+
probeProjectAccess(projectId) {
|
|
100
|
+
return this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** All Firebase projects visible to the authed user */
|
|
104
|
+
async listProjects() {
|
|
105
|
+
const response = await this.request(`${FIREBASE_API_BASE}/projects`);
|
|
106
|
+
return response.results || [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Organizations visible to the authed user — [] when there are none OR the
|
|
111
|
+
* search fails (org-less personal accounts are the normal case; a listing
|
|
112
|
+
* error must never block project creation).
|
|
113
|
+
*/
|
|
114
|
+
async listOrganizations() {
|
|
115
|
+
try {
|
|
116
|
+
const response = await this.request(`${RESOURCE_MANAGER_V1}/organizations:search`, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
body: JSON.stringify({}),
|
|
119
|
+
});
|
|
120
|
+
return response.organizations || [];
|
|
121
|
+
} catch {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create a GCP project (inside the organization when configured — that's
|
|
128
|
+
* what gives the compute service account its default roles) and add
|
|
129
|
+
* Firebase to it. Both are long-running operations, awaited.
|
|
130
|
+
*/
|
|
131
|
+
async createProject(projectId, displayName, organizationId = null) {
|
|
132
|
+
const payload = { projectId, name: displayName };
|
|
133
|
+
if (organizationId) {
|
|
134
|
+
payload.parent = { type: 'organization', id: organizationId };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const gcpResponse = await this.request(`${RESOURCE_MANAGER_V1}/projects`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
body: JSON.stringify(payload),
|
|
140
|
+
});
|
|
141
|
+
if (gcpResponse.name) {
|
|
142
|
+
await this.waitForOperation(gcpResponse.name, RESOURCE_MANAGER_V1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const firebaseResponse = await this.request(`${FIREBASE_API_BASE}/projects/${projectId}:addFirebase`, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
});
|
|
148
|
+
if (firebaseResponse.name) {
|
|
149
|
+
await this.waitForOperation(firebaseResponse.name, FIREBASE_API_BASE);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { projectId, displayName };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async listWebApps(projectId) {
|
|
156
|
+
const response = await this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps`);
|
|
157
|
+
return response.apps || [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Create a web app; waits for the operation and returns the appId */
|
|
161
|
+
async createWebApp(projectId, displayName) {
|
|
162
|
+
const response = await this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps`, {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
body: JSON.stringify({ displayName }),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (response.name) {
|
|
168
|
+
const result = await this.waitForOperation(response.name, FIREBASE_API_BASE);
|
|
169
|
+
return result?.appId;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return response.appId;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async updateWebAppDisplayName(projectId, appId, displayName) {
|
|
176
|
+
return this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps/${appId}?updateMask=displayName`, {
|
|
177
|
+
method: 'PATCH',
|
|
178
|
+
body: JSON.stringify({ displayName }),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** SDK config for an existing web app */
|
|
183
|
+
async getWebAppConfig(projectId, appId) {
|
|
184
|
+
return this.request(`${FIREBASE_API_BASE}/projects/${projectId}/webApps/${appId}/config`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ===========================================================================
|
|
188
|
+
// CLOUD RESOURCE MANAGER — GCP project identity + IAM policy
|
|
189
|
+
// ===========================================================================
|
|
190
|
+
|
|
191
|
+
/** GCP project (v3 — carries the editable displayName) */
|
|
192
|
+
async getGcpProject(projectId) {
|
|
193
|
+
try {
|
|
194
|
+
return await this.request(`${RESOURCE_MANAGER_V3}/projects/${projectId}`);
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async updateProjectName(projectId, displayName) {
|
|
201
|
+
return this.request(`${RESOURCE_MANAGER_V3}/projects/${projectId}?updateMask=displayName`, {
|
|
202
|
+
method: 'PATCH',
|
|
203
|
+
body: JSON.stringify({ displayName }),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Project number (required by the Service Usage API) */
|
|
208
|
+
async getProjectNumber(projectId) {
|
|
209
|
+
if (this._projectNumbers?.[projectId]) {
|
|
210
|
+
return this._projectNumbers[projectId];
|
|
211
|
+
}
|
|
212
|
+
const response = await this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}`);
|
|
213
|
+
this._projectNumbers = this._projectNumbers || {};
|
|
214
|
+
this._projectNumbers[projectId] = response.projectNumber;
|
|
215
|
+
return response.projectNumber;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async getIamPolicy(projectId) {
|
|
219
|
+
return this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}:getIamPolicy`, {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
body: JSON.stringify({ options: { requestedPolicyVersion: 3 } }),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async setIamPolicy(projectId, policy) {
|
|
226
|
+
return this.request(`${RESOURCE_MANAGER_V1}/projects/${projectId}:setIamPolicy`, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
body: JSON.stringify({ policy }),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ===========================================================================
|
|
233
|
+
// SERVICE USAGE — Google Cloud API enablement
|
|
234
|
+
// ===========================================================================
|
|
235
|
+
|
|
236
|
+
/** Names (e.g. 'firebase.googleapis.com') of all ENABLED services */
|
|
237
|
+
async listEnabledServices(projectId) {
|
|
238
|
+
const projectNumber = await this.getProjectNumber(projectId);
|
|
239
|
+
const enabled = [];
|
|
240
|
+
let pageToken = '';
|
|
241
|
+
|
|
242
|
+
do {
|
|
243
|
+
const url = `${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services?filter=state:ENABLED&pageSize=200${pageToken ? `&pageToken=${pageToken}` : ''}`;
|
|
244
|
+
const response = await this.request(url);
|
|
245
|
+
for (const service of response.services || []) {
|
|
246
|
+
// name is 'projects/{n}/services/{serviceName}'
|
|
247
|
+
enabled.push(service.name.split('/').pop());
|
|
248
|
+
}
|
|
249
|
+
pageToken = response.nextPageToken || '';
|
|
250
|
+
} while (pageToken);
|
|
251
|
+
|
|
252
|
+
return enabled;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async isServiceEnabled(projectId, serviceName) {
|
|
256
|
+
const projectNumber = await this.getProjectNumber(projectId);
|
|
257
|
+
try {
|
|
258
|
+
const response = await this.request(`${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services/${serviceName}`);
|
|
259
|
+
return response.state === 'ENABLED';
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async enableService(projectId, serviceName) {
|
|
266
|
+
const projectNumber = await this.getProjectNumber(projectId);
|
|
267
|
+
const response = await this.request(`${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services/${serviceName}:enable`, {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
body: JSON.stringify({}),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (response.name) {
|
|
273
|
+
await this.waitForOperation(response.name, SERVICE_USAGE_API_BASE);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { enabled: true, service: serviceName };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async enableServices(projectId, serviceNames) {
|
|
280
|
+
const projectNumber = await this.getProjectNumber(projectId);
|
|
281
|
+
const response = await this.request(`${SERVICE_USAGE_API_BASE}/projects/${projectNumber}/services:batchEnable`, {
|
|
282
|
+
method: 'POST',
|
|
283
|
+
body: JSON.stringify({ serviceIds: serviceNames }),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (response.name) {
|
|
287
|
+
await this.waitForOperation(response.name, SERVICE_USAGE_API_BASE);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { enabled: true, services: serviceNames };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ===========================================================================
|
|
294
|
+
// IAM — service accounts
|
|
295
|
+
// ===========================================================================
|
|
296
|
+
|
|
297
|
+
async listServiceAccounts(projectId) {
|
|
298
|
+
try {
|
|
299
|
+
const response = await this.request(`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts`);
|
|
300
|
+
return response.accounts || [];
|
|
301
|
+
} catch {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async createServiceAccount(projectId, accountId, displayName) {
|
|
307
|
+
return this.request(`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts`, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
body: JSON.stringify({
|
|
310
|
+
accountId,
|
|
311
|
+
serviceAccount: { displayName },
|
|
312
|
+
}),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create and download a service account key — returned decoded (Google
|
|
318
|
+
* only shows keys ONCE at creation; they cannot be re-downloaded)
|
|
319
|
+
*/
|
|
320
|
+
async createServiceAccountKey(projectId, serviceAccountEmail) {
|
|
321
|
+
const response = await this.request(`https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${serviceAccountEmail}/keys`, {
|
|
322
|
+
method: 'POST',
|
|
323
|
+
body: JSON.stringify({ privateKeyType: 'TYPE_GOOGLE_CREDENTIALS_FILE' }),
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (response.privateKeyData) {
|
|
327
|
+
return JSON.parse(Buffer.from(response.privateKeyData, 'base64').toString('utf8'));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return response;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ===========================================================================
|
|
334
|
+
// BILLING — Blaze plan
|
|
335
|
+
// ===========================================================================
|
|
336
|
+
|
|
337
|
+
async getProjectBillingInfo(projectId) {
|
|
338
|
+
try {
|
|
339
|
+
return await this.request(`${CLOUD_BILLING_API_BASE}/projects/${projectId}/billingInfo`);
|
|
340
|
+
} catch {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Billing accounts visible to the authed user (open + closed; callers mark closed) */
|
|
346
|
+
async listBillingAccounts() {
|
|
347
|
+
const response = await this.request(`${CLOUD_BILLING_API_BASE}/billingAccounts`);
|
|
348
|
+
return response.billingAccounts || [];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async linkBillingAccount(projectId, billingAccountName) {
|
|
352
|
+
return this.request(`${CLOUD_BILLING_API_BASE}/projects/${projectId}/billingInfo`, {
|
|
353
|
+
method: 'PUT',
|
|
354
|
+
body: JSON.stringify({ billingAccountName }),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ===========================================================================
|
|
359
|
+
// IAP — OAuth consent screen (brand)
|
|
360
|
+
// ===========================================================================
|
|
361
|
+
|
|
362
|
+
async listBrands(projectId) {
|
|
363
|
+
try {
|
|
364
|
+
const response = await this.request(`https://iap.googleapis.com/v1/projects/${projectId}/brands`);
|
|
365
|
+
return response.brands || [];
|
|
366
|
+
} catch {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* supportEmail must be the caller's email or a Google Group they own.
|
|
373
|
+
*
|
|
374
|
+
* The AUDIENCE cannot be asked for (#667, proven live): `orgInternalOnly`
|
|
375
|
+
* is output-only, an API-made brand is born Internal whatever the body
|
|
376
|
+
* says, and the create itself is org-only (an org-less project answers
|
|
377
|
+
* 400 "Project must belong to an organization"). The caller takes the
|
|
378
|
+
* returned brand through the audience stopper.
|
|
379
|
+
*/
|
|
380
|
+
async createBrand(projectId, applicationTitle, supportEmail) {
|
|
381
|
+
return this.request(`https://iap.googleapis.com/v1/projects/${projectId}/brands`, {
|
|
382
|
+
method: 'POST',
|
|
383
|
+
body: JSON.stringify({ applicationTitle, supportEmail }),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Email of the AUTHORIZING user — the only always-valid consent-screen
|
|
389
|
+
* supportEmail. Null when the cached token predates the userinfo.email
|
|
390
|
+
* scope (re-consent adds it).
|
|
391
|
+
*/
|
|
392
|
+
async getAuthenticatedEmail() {
|
|
393
|
+
try {
|
|
394
|
+
const info = await this.request('https://www.googleapis.com/oauth2/v2/userinfo');
|
|
395
|
+
return info?.email || null;
|
|
396
|
+
} catch {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ===========================================================================
|
|
402
|
+
// IDENTITY TOOLKIT — authentication configuration
|
|
403
|
+
// ===========================================================================
|
|
404
|
+
|
|
405
|
+
/** Upgrade Firebase Auth to Identity Platform (requires Blaze) */
|
|
406
|
+
async initializeIdentityPlatform(projectId) {
|
|
407
|
+
return this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/identityPlatform:initializeAuth`, {
|
|
408
|
+
method: 'POST',
|
|
409
|
+
body: JSON.stringify({}),
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async getIdentityConfig(projectId) {
|
|
414
|
+
try {
|
|
415
|
+
return await this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/config`);
|
|
416
|
+
} catch {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async updateIdentityConfig(projectId, config) {
|
|
422
|
+
const updateMask = Object.keys(config).join(',');
|
|
423
|
+
return this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/config?updateMask=${updateMask}`, {
|
|
424
|
+
method: 'PATCH',
|
|
425
|
+
body: JSON.stringify(config),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async getIdpConfig(projectId, idpId) {
|
|
430
|
+
try {
|
|
431
|
+
return await this.request(`${IDENTITY_TOOLKIT_API_BASE}/projects/${projectId}/defaultSupportedIdpConfigs/${idpId}`);
|
|
432
|
+
} catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ===========================================================================
|
|
438
|
+
// FIRESTORE
|
|
439
|
+
// ===========================================================================
|
|
440
|
+
|
|
441
|
+
async getFirestoreDatabase(projectId, databaseId = '(default)') {
|
|
442
|
+
try {
|
|
443
|
+
return await this.request(`${FIRESTORE_API_BASE}/projects/${projectId}/databases/${databaseId}`);
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async createFirestoreDatabase(projectId, locationId = 'nam5') {
|
|
450
|
+
const response = await this.request(`${FIRESTORE_API_BASE}/projects/${projectId}/databases?databaseId=(default)`, {
|
|
451
|
+
method: 'POST',
|
|
452
|
+
body: JSON.stringify({
|
|
453
|
+
type: 'FIRESTORE_NATIVE',
|
|
454
|
+
locationId,
|
|
455
|
+
}),
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if (response.name) {
|
|
459
|
+
await this.waitForOperation(response.name, FIRESTORE_API_BASE);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return response;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Enable Point-in-Time Recovery (7-day disaster recovery) */
|
|
466
|
+
async enableFirestorePITR(projectId, databaseId = '(default)') {
|
|
467
|
+
const response = await this.request(`${FIRESTORE_API_BASE}/projects/${projectId}/databases/${databaseId}?updateMask=pointInTimeRecoveryEnablement`, {
|
|
468
|
+
method: 'PATCH',
|
|
469
|
+
body: JSON.stringify({
|
|
470
|
+
pointInTimeRecoveryEnablement: 'POINT_IN_TIME_RECOVERY_ENABLED',
|
|
471
|
+
}),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Firestore answers this PATCH with an already-finished operation
|
|
475
|
+
// (done: true, the updated Database embedded) and purges the operation
|
|
476
|
+
// record immediately — polling its name 404s "Operation does not exist"
|
|
477
|
+
// from the first attempt (#164, recorded live 2026-08-03). Poll only a
|
|
478
|
+
// genuinely unfinished operation, and surface a finished one's error the
|
|
479
|
+
// way the poller would have.
|
|
480
|
+
if (response.done && response.error) {
|
|
481
|
+
throw new Error(`Operation failed: ${response.error.message}`);
|
|
482
|
+
}
|
|
483
|
+
if (!response.done && response.name?.includes('operations')) {
|
|
484
|
+
await this.waitForOperation(response.name, FIRESTORE_API_BASE);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return response;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ===========================================================================
|
|
491
|
+
// REALTIME DATABASE
|
|
492
|
+
// ===========================================================================
|
|
493
|
+
|
|
494
|
+
async listRealtimeDatabases(projectId) {
|
|
495
|
+
try {
|
|
496
|
+
const response = await this.request(`https://firebasedatabase.googleapis.com/v1beta/projects/${projectId}/locations/-/instances`);
|
|
497
|
+
return response.instances || [];
|
|
498
|
+
} catch {
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** The default instance must be named {projectId}-default-rtdb */
|
|
504
|
+
async createRealtimeDatabase(projectId, locationId = 'us-central1') {
|
|
505
|
+
const databaseId = `${projectId}-default-rtdb`;
|
|
506
|
+
return this.request(`https://firebasedatabase.googleapis.com/v1beta/projects/${projectId}/locations/${locationId}/instances?databaseId=${databaseId}`, {
|
|
507
|
+
method: 'POST',
|
|
508
|
+
body: JSON.stringify({ type: 'DEFAULT_DATABASE' }),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ===========================================================================
|
|
513
|
+
// STORAGE
|
|
514
|
+
// ===========================================================================
|
|
515
|
+
|
|
516
|
+
async getStorageBucket(projectId) {
|
|
517
|
+
try {
|
|
518
|
+
const response = await this.request(`https://firebasestorage.googleapis.com/v1beta/projects/${projectId}/defaultBucket`);
|
|
519
|
+
return response?.bucket || null;
|
|
520
|
+
} catch {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Create the default bucket via the Firebase Storage API (new buckets are
|
|
527
|
+
* {projectId}.firebasestorage.app; pre-Oct-2024 ones {projectId}.appspot.com)
|
|
528
|
+
*/
|
|
529
|
+
async createDefaultStorageBucket(projectId, location = 'us-central1') {
|
|
530
|
+
const existing = await this.getStorageBucket(projectId);
|
|
531
|
+
if (existing?.name) {
|
|
532
|
+
return { alreadyLinked: true, bucket: existing.name };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
const response = await this.request(`https://firebasestorage.googleapis.com/v1beta/projects/${projectId}/defaultBucket`, {
|
|
537
|
+
method: 'POST',
|
|
538
|
+
body: JSON.stringify({
|
|
539
|
+
location,
|
|
540
|
+
storageClass: 'STANDARD',
|
|
541
|
+
}),
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const bucketName = response?.bucket?.name || response?.name;
|
|
545
|
+
return { created: true, bucket: bucketName };
|
|
546
|
+
} catch (error) {
|
|
547
|
+
if (error.message?.includes('already') || error.message?.includes('409')) {
|
|
548
|
+
const bucket = await this.getStorageBucket(projectId);
|
|
549
|
+
return { alreadyLinked: true, bucket: bucket?.name };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ===========================================================================
|
|
557
|
+
// HOSTING — sites + custom domains
|
|
558
|
+
// ===========================================================================
|
|
559
|
+
|
|
560
|
+
async listHostingSites(projectId) {
|
|
561
|
+
const response = await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites`);
|
|
562
|
+
return response.sites || [];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async createHostingSite(projectId, siteId) {
|
|
566
|
+
return this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites?siteId=${siteId}`, {
|
|
567
|
+
method: 'POST',
|
|
568
|
+
body: JSON.stringify({}),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async getCustomDomain(projectId, siteId, domainName) {
|
|
573
|
+
const encodedDomain = encodeURIComponent(domainName);
|
|
574
|
+
try {
|
|
575
|
+
return await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites/${siteId}/customDomains/${encodedDomain}`);
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async createCustomDomain(projectId, siteId, domainName) {
|
|
582
|
+
const encodedDomain = encodeURIComponent(domainName);
|
|
583
|
+
const response = await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites/${siteId}/customDomains?customDomainId=${encodedDomain}`, {
|
|
584
|
+
method: 'POST',
|
|
585
|
+
body: JSON.stringify({}),
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
return this.settleDomainClaim(response);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async undeleteCustomDomain(projectId, siteId, domainName) {
|
|
592
|
+
const encodedDomain = encodeURIComponent(domainName);
|
|
593
|
+
const response = await this.request(`${FIREBASE_HOSTING_API_BASE}/projects/${projectId}/sites/${siteId}/customDomains/${encodedDomain}:undelete`, {
|
|
594
|
+
method: 'POST',
|
|
595
|
+
body: JSON.stringify({}),
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
return this.settleDomainClaim(response);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Wait on a domain claim's operation, briefly. The claim only completes once
|
|
603
|
+
* DNS verification lands (hours away, normally), so the budget is short: long
|
|
604
|
+
* enough to surface an operation that fails outright, then the claim is
|
|
605
|
+
* reported PENDING (#56) and hosting's own verification poller takes over.
|
|
606
|
+
*/
|
|
607
|
+
async settleDomainClaim(response) {
|
|
608
|
+
if (!response.name?.includes('operations')) {
|
|
609
|
+
return response;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const outcome = await this.waitForOperation(response.name, FIREBASE_HOSTING_API_BASE, DOMAIN_CLAIM_POLL);
|
|
613
|
+
|
|
614
|
+
return outcome?.pending ? { ...response, pending: true } : response;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Domain verification status:
|
|
619
|
+
* { exists, verified, deleted?, ownershipState, hostState, certState, requiredDnsUpdates }
|
|
620
|
+
*/
|
|
621
|
+
async checkDomainStatus(projectId, siteId, domainName) {
|
|
622
|
+
const domain = await this.getCustomDomain(projectId, siteId, domainName);
|
|
623
|
+
|
|
624
|
+
if (!domain) {
|
|
625
|
+
return { verified: false, exists: false };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (domain.deleteTime) {
|
|
629
|
+
return { verified: false, exists: false, deleted: true };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const ownershipActive = domain.ownershipState === 'OWNERSHIP_ACTIVE';
|
|
633
|
+
const hostActive = domain.hostState === 'HOST_ACTIVE';
|
|
634
|
+
const verified = ownershipActive && hostActive;
|
|
635
|
+
|
|
636
|
+
// requiredDnsUpdates: { discovered: [...], desired: [...] } — we want desired
|
|
637
|
+
let dnsUpdates = [];
|
|
638
|
+
if (domain.requiredDnsUpdates) {
|
|
639
|
+
if (Array.isArray(domain.requiredDnsUpdates.desired)) {
|
|
640
|
+
dnsUpdates = domain.requiredDnsUpdates.desired;
|
|
641
|
+
} else if (Array.isArray(domain.requiredDnsUpdates)) {
|
|
642
|
+
dnsUpdates = domain.requiredDnsUpdates;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ACME challenge for SSL rides in cert.verification.dns.desired
|
|
647
|
+
if (Array.isArray(domain.cert?.verification?.dns?.desired)) {
|
|
648
|
+
dnsUpdates = [...dnsUpdates, ...domain.cert.verification.dns.desired];
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
exists: true,
|
|
653
|
+
verified,
|
|
654
|
+
ownershipState: domain.ownershipState,
|
|
655
|
+
certState: domain.certState,
|
|
656
|
+
hostState: domain.hostState,
|
|
657
|
+
requiredDnsUpdates: dnsUpdates,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
module.exports = { FirebaseAPI };
|