- TypeScript 78.3%
- JavaScript 17.7%
- CSS 2.3%
- Shell 1.4%
- HTML 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| .vscode | ||
| chrome | ||
| contrib | ||
| firefox | ||
| geckoview | ||
| icons | ||
| shared | ||
| test | ||
| .editorconfig | ||
| .git-blame-ignore-revs | ||
| .gitignore | ||
| build.sh | ||
| bun.lock | ||
| bunfig.toml | ||
| eslint.config.js | ||
| lefthook.yml | ||
| LICENSE | ||
| package.json | ||
| prettier.config.js | ||
| README.md | ||
Grindr Google OAuth WebExtension
Note
If you're an Android user, you might be looking for Open Grind or Open Grind Google OAuth Android app instead.
WebExtension that gets OAuth access token via Google Identity Services (GIS) for Grindr sign-in. Made possible by the fact that GIS refuses to run in embedded app WebViews, and GeckoView forbids the host app from injecting JS into pages.
Install
- Firefox, Librewolf (desktop & Android):
- Download from addons.mozilla.org (recommended)
- or download
grindr_google_oauth-x.x.x-firefox.zipfrom Releases, unarchive, load the unpacked directory viaabout:debugging(This Firefox → "Load Temporary Add-on")
- Google Chrome, Chromium & Chromium-based browsers (desktop only):
- Download from chrome.google.com (recommended)
- or download
grindr_google_oauth-x.x.x-chrome.zipfrom Releases, unarchive, load the unpacked directory viachrome://extensions(Developer mode → "Load unpacked")
- GeckoView (for developers embedding this project into their Android app):
- Download
grindr_google_oauth-x.x.x-geckoview.zipfrom Releases, bundle the builtweb-ext-artifacts/geckoview/directory in your app'sassets/and install it as a built-in extension viaruntime.webExtensionController.ensureBuiltIn(uri, id)(nativeMessagingandgeckoViewAddonsare privileged permissions that only work for a built-in extension)
- Download
Alternatively, clone the repository yourself and build the extension using ./build.sh [firefox|chrome|geckoview] (requires Bun).
Usage
Firefox, Librewolf (desktop & Android) and Google Chrome, Chromium & Chromium-based browsers (desktop only):
- Install the extension
- Click the toolbar icon
- A new tab opens with a button
- Click "Sign in with Google"
- Complete the OAuth flow in the Google window that opens
- Tap "Copy token" on the page and paste it into the host app
GeckoView:
- Install the extension as a built-in (see above) and load
https://web.grindr.com/in aGeckoSession. If the session is private, allow the extension in private browsing. - The content script blanks the page and shows a "Sign in with Google" button. After the user signs in, the token is sent to your app over native messaging.
- Register the delegate on the extension:
runtime.webExtensionController
.ensureBuiltIn(
"resource://android/assets/grindr-google-oauth/",
"grindr-google-oauth-webextension@opengrind.org",
)
.accept { extension ->
extension?.setMessageDelegate(delegate, "grindr_google_oauth")
runtime.webExtensionController.setAllowedInPrivateBrowsing(extension!!, true)
}
The delegate receives:
| Message | Meaning |
|---|---|
{ "type": "token", "token": "ya29..." } |
the access token |
{ "type": "error", "error": "..." } |
sign-in failed; the reason is already on screen in the page |
Answer every message from onMessage:
GeckoResult.fromValue(true)once you have taken the token, or for an error you have notedGeckoResult.fromException(...)to refuse it, which puts the page back on "Try again"- Any other reply, including
falseornull, also counts as taken. The page then stays on "Signing in with Google...", so move the session on (the reference app loadsshared/token.html#<token>) - Reply with a primitive. A
JSONObjectreply fails with "Invalid event data for callback" and the page hangs until it times out. - Reply within 10 seconds. After that the page shows "The app didn't answer." and re-enables the button.
One delegate is kept per runtime, per extension id and native app name, and the last registration wins. If more than one activity shares the runtime, register again in onResume(), or a finished activity keeps the delegate and tokens go nowhere. GeckoView also queues messages sent while no delegate is registered, so register before loading the page.
A session-level delegate (session.webExtensionController.setMessageDelegate(extension, delegate, name)) receives messages from extension pages in that session. This extension sends none, so you do not need one.
The token is then to be used with the /v8/sessions/thirdparty endpoint.
