@t8n/google-oauth
v1.4.1
Published
Lightweight Google OAuth2 client for TitanPL
Readme
@t8n/google-oauth
Lightweight, high-performance Google OAuth2 and Gmail API client for TitanPL. This extension provides a synchronous Developer Experience (DX) for handling Google authentication and email data using the native @titanpl/surface HTTP module.
Features
- 🔐 OAuth2 Flow: Easy sign-in and callback handling.
- 📧 Gmail Integration: List, send, archive, and move emails with full data.
- 🖼️ Google Photos: Access albums and media items effortlessly.
- 🔍 Advanced Search: Filter emails using powerful Gmail search queries.
- 📎 Attachments: Comprehensive support for listing and downloading email attachments.
- 🔄 Token Refresh: Effortlessly generate new access tokens from refresh tokens.
- 🛡️ ID Token Verification: Securely verify Google
id_tokens. - 🛡️ JWT Parsing: Built-in utility to decode Google ID tokens.
- 🚀 Titan Native: Optimized for Titan Surface and Gravity runtime.
- 📂 Modular Structure: Cleanly split into
gmail,photos, andhelpers.
Installation
titan install @t8n/google-oauthQuick Start
1. Initialization
import Google from "@t8n/google-oauth";
const google = new Google({
clientId: Titan.env.GOOGLE_CLIENT_ID,
clientSecret: Titan.env.GOOGLE_CLIENT_SECRET,
redirectUri: "http://localhost:5100/callback",
scope: "openid email profile https://www.googleapis.com/auth/gmail.readonly"
});2. Sign In Flow
// action: login.js
export default defineAction((req) => {
// You can pass a 'state' parameter to maintain state between login and callback
return google.signIn({ state: "my-custom-state" });
});
// action: callback.js
export default defineAction((req) => {
// Google returns 'code' and 'state' in the query
const { code, state } = req.query;
const result = google.callback(code);
// Returns { user, access_token, refresh_token }
return result;
});3. Gmail API Usage
Listing Emails
List recent emails with full metadata and attachments.
// action: emails.js
export default defineAction((req) => {
const token = req.query.token;
// Fetch 5 emails that match multiple criteria
const emails = google.gmail.messages.list(token, {
count: 5,
q: ["has:attachment", "from:[email protected]"]
});
return emails;
});Sending Emails
// action: send.js
export default defineAction((req) => {
const token = req.query.token;
return google.gmail.messages.send(token, {
to: "[email protected]",
subject: "Hello from TitanPL",
body: "<h1>This is a test email</h1><p>Sent via @t8n/google-oauth</p>",
cc: "[email protected]"
});
});Managing Messages
// Mark as read (remove UNREAD label)
google.gmail.messages.modify(token, messageId, {
removeLabelIds: ["UNREAD"]
});
// Archive (remove INBOX label)
google.gmail.messages.archive(token, messageId);
// Move to trash
google.gmail.messages.trash(token, messageId);4. Google Photos API Usage
Access albums and media items. Ensure you have the appropriate scopes (e.g., https://www.googleapis.com/auth/photoslibrary.readonly).
// action: photos.js
export default defineAction((req) => {
const token = req.query.token;
// List albums
const albums = google.photos.albums.list(token, { count: 10 });
// Search for media items in a specific album
const media = google.photos.mediaItems.search(token, {
albumId: "ALBUM_ID"
});
return { albums, media };
});5. Downloading Attachments
Fetch attachment content as a Data URL for preview or raw data.
// action: download.js
export default defineAction((req) => {
const { token, msgId, attId, mimeType, filename } = req.query;
const result = google.gmail.messages.getAttachment(
token,
msgId,
attId,
mimeType,
filename
);
// result.url contains a Data URL for images/PDFs, or raw base64 for others
return result.url;
});5. Refreshing Tokens
// action: refresh.js
export default defineAction((req) => {
const refreshToken = req.query.refresh_token;
const newTokens = google.refreshToken(refreshToken);
return newTokens.access_token;
});6. Verifying ID Tokens
If you are implementing your own flow or need to verify a user's identity securely, you should verify the id_token. Google returns an id_token only when you include the openid scope.
// action: verify.js
export default defineAction((req) => {
const idToken = req.query.id_token;
// Verifies the token cryptographically via Google's tokeninfo endpoint
// Throws an Error if the token is invalid or expired
const decoded = google.verifyIdToken(idToken);
return {
id: decoded.sub,
email: decoded.email,
name: decoded.name,
picture: decoded.picture
};
});API Reference
new Google(config)
Initializes the Google client.
clientId: Google Client ID.clientSecret: Google Client Secret.redirectUri: Callback URL.scope: Space-separated scopes.
google.signIn(options)
Returns a redirect response to the Google Consent screen.
options.state: (Optional) Opaque value passed to the callback.
google.callback(code)
Exchanges authorization code for tokens and user profile.
- Returns:
GoogleAuthResult { user, access_token, refresh_token }.
google.refreshToken(token)
Generates a new access token.
- Returns:
{ access_token, expires_in, scope, token_type, id_token }.
google.verifyIdToken(idToken)
Verifies an ID token against Google's tokeninfo endpoint. Throws an error if invalid.
- Returns: Decoded JWT payload (e.g.,
{ sub, email, name, picture }).
google.gmail.messages.list(accessToken, options)
Fetches a list of messages with full data.
- Returns:
GmailMessage[].
google.gmail.messages.send(accessToken, email)
Sends a new message.
email.to: Recipient email.email.subject: Email subject.email.body: HTML or plain text body.email.cc/bcc: (Optional) CC/BCC recipients.
google.gmail.messages.trash/untrash/delete(accessToken, messageId)
Manage message lifecycle.
google.gmail.messages.modify(accessToken, messageId, mods)
Modify labels. mods is { addLabelIds, removeLabelIds }.
google.gmail.threads.list/get/trash/untrash/delete(accessToken, ...)
Manage email threads.
google.gmail.messages.getAttachment(accessToken, msgId, attId, mimeType?, filename?)
Fetches a specific attachment.
mimeType: Optional. Used to generate Data URLs for visual files.filename: Optional. Included in the result object.- Returns:
{ url, filename, mimeType, raw }.
GmailMessage Object
| Property | Description |
| :--- | :--- |
| id | Message ID |
| threadId | Thread ID |
| from | Sender address |
| to | Recipient address |
| subject | Email subject |
| snippet | Preview text |
| body | Full HTML/Plain body |
| text | Plain text body |
| date | Date string |
| isRead | Boolean status |
| labels | Array of Gmail labels (e.g., ["INBOX", "UNREAD"]) |
| attachments | GmailAttachment[] |
| timestamp | Internal date in ms |
GmailAttachment Object
| Property | Description |
| :--- | :--- |
| id | Attachment ID |
| filename | Original filename |
| mimeType | File MIME type |
| size | File size in bytes |
| url | API URL for direct fetching |
Search Queries
The q parameter supports all standard Gmail search operators:
has:attachment: Only emails with files.from:[email protected]: From a specific sender.is:unread: Only unread emails.after:2023/01/01: Emails after a specific date.subject:(Meeting): Search in subject.
Error Handling
When using drift() (implicit in this library), TitanPL may throw a __SUSPEND__ signal. If you wrap calls in try-catch, ensure you re-throw this signal:
try {
const emails = google.gmail.messages.list(token);
} catch (e) {
if (e === "__SUSPEND__" || e.message === "__SUSPEND__") throw e;
// handle actual error
}