Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed bug where `functions:secrets:set` didn't remove stale versions of a secret. (#6080)
- Fixed bug where `firebase deploy --only firestore:named-db` didn't update rules. (#6129)
- Fixed issue where Flutter Web is not detected as a web framework. (#6085)
- Add better messages for API permissions failures that direct the user to the URL to enable the API. (#6130)
4 changes: 4 additions & 0 deletions firebase-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 0.0.24-alpha.1

- Add more user-friendly API permission denied messages

## 0.0.24-alpha.0

- Remove Google sign-in option from Monospace.
Expand Down
4 changes: 2 additions & 2 deletions firebase-vscode/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion firebase-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "firebase-vscode",
"publisher": "firebase",
"description": "VSCode Extension for Firebase",
"version": "0.0.24-alpha.0",
"version": "0.0.24-alpha.1",
"engines": {
"vscode": "^1.69.0"
},
Expand Down
2 changes: 1 addition & 1 deletion firebase-vscode/src/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export async function setupWorkflow(
readAndSendFirebaseConfigs(broker, context);
broker.send("notifyHostingInitDone",
{ success, projectId, folderPath: currentOptions.cwd, framework: currentFramework });
await fetchChannels(true);
await fetchChannels(broker, true);
} else {
broker.send("notifyHostingInitDone",
{ success, projectId, folderPath: currentOptions.cwd });
Expand Down
28 changes: 27 additions & 1 deletion src/ensureApiEnabled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export async function check(
return isEnabled;
}

function isPermissionError(e: { context?: { body?: { error?: { status?: string } } } }): boolean {
return e.context?.body?.error?.status === "PERMISSION_DENIED";
}

/**
* Attempt to enable an API on the specified project (just once).
*
Expand Down Expand Up @@ -68,8 +72,30 @@ async function enable(projectId: string, apiName: string): Promise<void> {
)} can't be enabled until the upgrade is complete. To upgrade, visit the following URL:

https://console.firebase.google.com/project/${projectId}/usage/details`);
} else if (isPermissionError(err)) {
const apiPermissionDeniedRegex = new RegExp(
/Permission denied to enable service \[([.a-zA-Z]+)\]/
);
// Recognize permission denied errors on APIs and provide users the
// GCP console link to easily enable the API.
const permissionsError = apiPermissionDeniedRegex.exec((err as Error).message);
if (permissionsError && permissionsError[1]) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wont this fail if permissionsError.length <2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but if it fails that means my regex failed and I don't have a capturing group with the string of the API name, so I can't go forward with constructing this message anyway. It will fall back to throwing the original error which should be the most useful thing we have.

const serviceUrl = permissionsError[1];
// Expand the error message instead of creating a new error so that
// all the other error properties (status, context, etc) are passed
// downstream to anything that uses them.
(err as Error).message = `Permissions denied enabling ${serviceUrl}.
Please ask a project owner to visit the following URL to enable this service:

https://console.cloud.google.com/apis/library/${serviceUrl}?project=${projectId}`;
throw err;
} else {
// Regex failed somehow - show the raw permissions error.
throw err;
}
} else {
throw err;
}
throw err;
}
}

Expand Down