Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ dist-ssr
test-report.xml

.nvmrc
coverage/
coverage/
deploy/extension/.env
3 changes: 3 additions & 0 deletions deploy/extension/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GOOGLE_CLIENT_ID=your-client-id-here
GOOGLE_CLIENT_SECRET=your-client-secret-here
GOOGLE_AUTH_CODE=your-auth-code-here
43 changes: 43 additions & 0 deletions deploy/extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Chrome Web Store - Generate Refresh Token

Generate a Google OAuth refresh token for publishing the extension to the Chrome Web Store via CI.

## Prerequisites

- Python 3 with `python-dotenv` installed (`pip install python-dotenv`)
- Access to the Google Cloud project that owns the OAuth client
- The Google account used must have **publisher/editor access** to the extension in the [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole)

## Steps

### 1. Set up environment variables

```bash
cp .env.example .env
```

Edit `.env` and fill in `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` from the [Google Cloud Console](https://console.cloud.google.com/) under **APIs & Services > Clients**.

### 2. Get an authorization code

Make sure `http://localhost:8818` is listed as an **Authorized redirect URI** in the Google Cloud Console OAuth client settings.

Open the following URL in your browser, replacing `YOUR_CLIENT_ID` with your actual client ID:

```
https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https://www.googleapis.com/auth/chromewebstore&client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8818&access_type=offline&prompt=consent
```

After authorizing, the browser will redirect to `http://localhost:8818/?code=XXXX&scope=...`. Copy the `code` value from the URL.

### 3. Exchange for refresh token

Paste the authorization code into `.env` as `GOOGLE_AUTH_CODE`, then run:

```bash
python3 get_refresh_token.py
```

The script will output the refresh token. Update this value in AWS Secrets Manager (`GOOGLE_REFRESH_TOKEN`).

> **Note:** Authorization codes are single-use and expire within a few minutes. If you get an `invalid_grant` error, repeat step 2 to get a fresh code and run the script immediately.
48 changes: 48 additions & 0 deletions deploy/extension/get_refresh_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import os
import json
import urllib.parse
import urllib.request
import urllib.error

from dotenv import load_dotenv

load_dotenv()

CLIENT_ID = os.environ["GOOGLE_CLIENT_ID"]
CLIENT_SECRET = os.environ["GOOGLE_CLIENT_SECRET"]
AUTH_CODE = os.environ["GOOGLE_AUTH_CODE"]


def exchange_code_for_token():
data = urllib.parse.urlencode(
{
"client_id": CLIENT_ID.strip(),
"client_secret": CLIENT_SECRET.strip(),
"code": AUTH_CODE.strip(),
"grant_type": "authorization_code",
"redirect_uri": "http://localhost:8818",
}
).encode()

req = urllib.request.Request(
"https://accounts.google.com/o/oauth2/token",
data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"},
)

try:
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
except urllib.error.HTTPError as e:
result = json.loads(e.read())

print(json.dumps(result, indent=2))

if "error" not in result:
print(f"\nRefresh token:\n{result['refresh_token']}")
else:
print(f"\nError: {result['error']} - {result.get('error_description', '')}")


if __name__ == "__main__":
exchange_code_for_token()
Loading