@bubblebridge/sdk 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +94 -2
- package/dist/calendar.d.ts +17 -0
- package/dist/calendar.js +25 -0
- package/dist/error.d.ts +14 -0
- package/dist/error.js +36 -0
- package/dist/generated.d.ts +1392 -0
- package/dist/generated.js +5 -0
- package/dist/gmail.d.ts +16 -0
- package/dist/gmail.js +26 -0
- package/dist/google.d.ts +13 -0
- package/dist/google.js +15 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +24 -0
- package/dist/oauth.d.ts +25 -0
- package/dist/oauth.js +50 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.js +1 -0
- package/dist/user.d.ts +25 -0
- package/dist/user.js +59 -0
- package/package.json +41 -4
package/dist/gmail.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Google } from './google.js';
|
|
2
|
+
/** One plain text email from the user's account. */
|
|
3
|
+
export interface SendEmail {
|
|
4
|
+
to: string;
|
|
5
|
+
subject: string;
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
/** The Gmail helpers. `Google.gmail` holds it. */
|
|
9
|
+
export declare class Gmail {
|
|
10
|
+
private readonly google;
|
|
11
|
+
constructor(google: Google);
|
|
12
|
+
/** Sends the email. It returns the Gmail message resource. */
|
|
13
|
+
send(email: SendEmail): Promise<unknown>;
|
|
14
|
+
/** Lists the message IDs that match a Gmail search query. */
|
|
15
|
+
listMessages(query: string, maxResults: number): Promise<unknown>;
|
|
16
|
+
}
|
package/dist/gmail.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The Gmail helpers. `Google.gmail` holds it. */
|
|
2
|
+
export class Gmail {
|
|
3
|
+
google;
|
|
4
|
+
constructor(google) {
|
|
5
|
+
this.google = google;
|
|
6
|
+
}
|
|
7
|
+
/** Sends the email. It returns the Gmail message resource. */
|
|
8
|
+
send(email) {
|
|
9
|
+
const message = `To: ${email.to}\r\nSubject: ${email.subject}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${email.text}`;
|
|
10
|
+
return this.google.request('POST', '/gmail/v1/users/me/messages/send', {
|
|
11
|
+
raw: base64Url(message),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** Lists the message IDs that match a Gmail search query. */
|
|
15
|
+
listMessages(query, maxResults) {
|
|
16
|
+
const params = new URLSearchParams({ q: query, maxResults: String(maxResults) });
|
|
17
|
+
return this.google.request('GET', `/gmail/v1/users/me/messages?${params}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function base64Url(text) {
|
|
21
|
+
const bytes = new TextEncoder().encode(text);
|
|
22
|
+
let binary = '';
|
|
23
|
+
for (const byte of bytes)
|
|
24
|
+
binary += String.fromCharCode(byte);
|
|
25
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
26
|
+
}
|
package/dist/google.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Calendar } from './calendar.js';
|
|
2
|
+
import { Gmail } from './gmail.js';
|
|
3
|
+
import type { Method } from './types.js';
|
|
4
|
+
import type { UserClient } from './user.js';
|
|
5
|
+
/** The Google provider of one user. `UserClient.google` holds it. */
|
|
6
|
+
export declare class Google {
|
|
7
|
+
private readonly user;
|
|
8
|
+
readonly gmail: Gmail;
|
|
9
|
+
readonly calendar: Calendar;
|
|
10
|
+
constructor(user: UserClient);
|
|
11
|
+
/** Sends one call to any Google path on the allowlist through the proxy. */
|
|
12
|
+
request<T = unknown>(method: Method, path: string, body?: unknown): Promise<T>;
|
|
13
|
+
}
|
package/dist/google.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Calendar } from './calendar.js';
|
|
2
|
+
import { Gmail } from './gmail.js';
|
|
3
|
+
/** The Google provider of one user. `UserClient.google` holds it. */
|
|
4
|
+
export class Google {
|
|
5
|
+
user;
|
|
6
|
+
gmail = new Gmail(this);
|
|
7
|
+
calendar = new Calendar(this);
|
|
8
|
+
constructor(user) {
|
|
9
|
+
this.user = user;
|
|
10
|
+
}
|
|
11
|
+
/** Sends one call to any Google path on the allowlist through the proxy. */
|
|
12
|
+
request(method, path, body) {
|
|
13
|
+
return this.user.send(method, `/v1/providers/google${path}`, body);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Oauth } from './oauth.js';
|
|
2
|
+
import { UserClient, type ForUserOptions } from './user.js';
|
|
3
|
+
import type { TokenResponse } from './types.js';
|
|
4
|
+
export type { components, operations, paths } from './generated.js';
|
|
5
|
+
export type { Connection, ConnectionList, ConnectionStatus, Method, Scope, TokenResponse, } from './types.js';
|
|
6
|
+
export { ApiError, UpstreamError } from './error.js';
|
|
7
|
+
export { Oauth, type AuthorizeUrlInput, type ExchangeCodeInput } from './oauth.js';
|
|
8
|
+
export { UserClient, type ForUserOptions, type OnTokensUpdated } from './user.js';
|
|
9
|
+
export { Google } from './google.js';
|
|
10
|
+
export { Gmail, type SendEmail } from './gmail.js';
|
|
11
|
+
export { Calendar, type NewEvent } from './calendar.js';
|
|
12
|
+
export interface BubbleBridgeOptions {
|
|
13
|
+
clientId: string;
|
|
14
|
+
clientSecret: string;
|
|
15
|
+
}
|
|
16
|
+
/** One client for one agent. It holds the client ID and the client secret of the agent. */
|
|
17
|
+
export declare class BubbleBridge {
|
|
18
|
+
readonly clientId: string;
|
|
19
|
+
readonly clientSecret: string;
|
|
20
|
+
readonly baseUrl = "https://bubblebridge.io";
|
|
21
|
+
readonly apiUrl = "https://api.bubblebridge.io";
|
|
22
|
+
readonly oauth: Oauth;
|
|
23
|
+
constructor({ clientId, clientSecret }: BubbleBridgeOptions);
|
|
24
|
+
/** A client for one user. `onTokensUpdated` runs after each token rotation. */
|
|
25
|
+
forUser(tokens: TokenResponse, options?: ForUserOptions): UserClient;
|
|
26
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Oauth } from './oauth.js';
|
|
2
|
+
import { UserClient } from './user.js';
|
|
3
|
+
export { ApiError, UpstreamError } from './error.js';
|
|
4
|
+
export { Oauth } from './oauth.js';
|
|
5
|
+
export { UserClient } from './user.js';
|
|
6
|
+
export { Google } from './google.js';
|
|
7
|
+
export { Gmail } from './gmail.js';
|
|
8
|
+
export { Calendar } from './calendar.js';
|
|
9
|
+
/** One client for one agent. It holds the client ID and the client secret of the agent. */
|
|
10
|
+
export class BubbleBridge {
|
|
11
|
+
clientId;
|
|
12
|
+
clientSecret;
|
|
13
|
+
baseUrl = 'https://bubblebridge.io';
|
|
14
|
+
apiUrl = 'https://api.bubblebridge.io';
|
|
15
|
+
oauth = new Oauth(this);
|
|
16
|
+
constructor({ clientId, clientSecret }) {
|
|
17
|
+
this.clientId = clientId;
|
|
18
|
+
this.clientSecret = clientSecret;
|
|
19
|
+
}
|
|
20
|
+
/** A client for one user. `onTokensUpdated` runs after each token rotation. */
|
|
21
|
+
forUser(tokens, options = {}) {
|
|
22
|
+
return new UserClient(this, tokens, options.onTokensUpdated);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/oauth.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BubbleBridge } from './index.js';
|
|
2
|
+
import type { Scope, TokenResponse } from './types.js';
|
|
3
|
+
export interface AuthorizeUrlInput {
|
|
4
|
+
scopes: Scope[];
|
|
5
|
+
redirectUri: string;
|
|
6
|
+
state: string;
|
|
7
|
+
codeChallenge: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ExchangeCodeInput {
|
|
10
|
+
code: string;
|
|
11
|
+
codeVerifier: string;
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
}
|
|
14
|
+
/** The OAuth functions of a client. `BubbleBridge.oauth` holds it. */
|
|
15
|
+
export declare class Oauth {
|
|
16
|
+
private readonly client;
|
|
17
|
+
constructor(client: BubbleBridge);
|
|
18
|
+
/** The URL of the consent page. The agent site sends the browser to it. */
|
|
19
|
+
authorizeUrl({ scopes, redirectUri, state, codeChallenge }: AuthorizeUrlInput): string;
|
|
20
|
+
/** Exchanges the authorization code for the first token pair. */
|
|
21
|
+
exchangeCode({ code, codeVerifier, redirectUri }: ExchangeCodeInput): Promise<TokenResponse>;
|
|
22
|
+
/** Exchanges a refresh token for a new token pair. The API rotates the refresh token. */
|
|
23
|
+
refresh(refreshToken: string): Promise<TokenResponse>;
|
|
24
|
+
private token;
|
|
25
|
+
}
|
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ApiError } from './error.js';
|
|
2
|
+
/** The OAuth functions of a client. `BubbleBridge.oauth` holds it. */
|
|
3
|
+
export class Oauth {
|
|
4
|
+
client;
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
}
|
|
8
|
+
/** The URL of the consent page. The agent site sends the browser to it. */
|
|
9
|
+
authorizeUrl({ scopes, redirectUri, state, codeChallenge }) {
|
|
10
|
+
const query = new URLSearchParams({
|
|
11
|
+
client_id: this.client.clientId,
|
|
12
|
+
redirect_uri: redirectUri,
|
|
13
|
+
scope: scopes.join(' '),
|
|
14
|
+
state,
|
|
15
|
+
code_challenge: codeChallenge,
|
|
16
|
+
});
|
|
17
|
+
return `${this.client.baseUrl}/authorize?${query}`;
|
|
18
|
+
}
|
|
19
|
+
/** Exchanges the authorization code for the first token pair. */
|
|
20
|
+
exchangeCode({ code, codeVerifier, redirectUri }) {
|
|
21
|
+
return this.token({
|
|
22
|
+
grant_type: 'authorization_code',
|
|
23
|
+
code,
|
|
24
|
+
code_verifier: codeVerifier,
|
|
25
|
+
redirect_uri: redirectUri,
|
|
26
|
+
client_id: this.client.clientId,
|
|
27
|
+
client_secret: this.client.clientSecret,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Exchanges a refresh token for a new token pair. The API rotates the refresh token. */
|
|
31
|
+
refresh(refreshToken) {
|
|
32
|
+
return this.token({
|
|
33
|
+
grant_type: 'refresh_token',
|
|
34
|
+
refresh_token: refreshToken,
|
|
35
|
+
client_id: this.client.clientId,
|
|
36
|
+
client_secret: this.client.clientSecret,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async token(request) {
|
|
40
|
+
const response = await fetch(`${this.client.apiUrl}/oauth/token`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'content-type': 'application/json' },
|
|
43
|
+
body: JSON.stringify(request),
|
|
44
|
+
});
|
|
45
|
+
if (response.ok)
|
|
46
|
+
return (await response.json());
|
|
47
|
+
const { error } = (await response.json());
|
|
48
|
+
throw new ApiError(response.status, error);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { components } from './generated.js';
|
|
2
|
+
export type Scope = components['schemas']['Scope'];
|
|
3
|
+
export type TokenRequest = components['schemas']['TokenRequest'];
|
|
4
|
+
export type TokenResponse = components['schemas']['TokenResponse'];
|
|
5
|
+
export type ErrorBody = components['schemas']['ErrorBody'];
|
|
6
|
+
export type ErrorResponse = components['schemas']['ErrorResponse'];
|
|
7
|
+
export type Connection = components['schemas']['Connection'];
|
|
8
|
+
export type ConnectionList = components['schemas']['ConnectionList'];
|
|
9
|
+
export type ConnectionStatus = components['schemas']['ConnectionStatus'];
|
|
10
|
+
export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/user.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Google } from './google.js';
|
|
2
|
+
import type { BubbleBridge } from './index.js';
|
|
3
|
+
import type { ConnectionList, Method, TokenResponse } from './types.js';
|
|
4
|
+
/** The builder stores the new token pair in this callback. It runs after each rotation. */
|
|
5
|
+
export type OnTokensUpdated = (tokens: TokenResponse) => void;
|
|
6
|
+
export interface ForUserOptions {
|
|
7
|
+
onTokensUpdated?: OnTokensUpdated;
|
|
8
|
+
}
|
|
9
|
+
/** One client for one user. `BubbleBridge.forUser` returns it. */
|
|
10
|
+
export declare class UserClient {
|
|
11
|
+
readonly client: BubbleBridge;
|
|
12
|
+
private readonly onTokensUpdated?;
|
|
13
|
+
readonly google: Google;
|
|
14
|
+
private tokens;
|
|
15
|
+
private expiresAt;
|
|
16
|
+
private refreshing?;
|
|
17
|
+
constructor(client: BubbleBridge, tokens: TokenResponse, onTokensUpdated?: OnTokensUpdated | undefined);
|
|
18
|
+
/** A valid access token. It refreshes the token pair first if the access token has expired. */
|
|
19
|
+
accessToken(): Promise<string>;
|
|
20
|
+
/** The connections that the user enabled for the agent. */
|
|
21
|
+
connections(): Promise<ConnectionList>;
|
|
22
|
+
/** Sends one call to the API with the agent access token. */
|
|
23
|
+
send<T>(method: Method, pathAndQuery: string, body?: unknown): Promise<T>;
|
|
24
|
+
private refresh;
|
|
25
|
+
}
|
package/dist/user.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { errorFromResponse } from './error.js';
|
|
2
|
+
import { Google } from './google.js';
|
|
3
|
+
/** The proxy may receive the token a few seconds after the SDK reads the expiry. */
|
|
4
|
+
const REFRESH_MARGIN_MS = 60_000;
|
|
5
|
+
/** One client for one user. `BubbleBridge.forUser` returns it. */
|
|
6
|
+
export class UserClient {
|
|
7
|
+
client;
|
|
8
|
+
onTokensUpdated;
|
|
9
|
+
google = new Google(this);
|
|
10
|
+
tokens;
|
|
11
|
+
expiresAt;
|
|
12
|
+
refreshing;
|
|
13
|
+
constructor(client, tokens, onTokensUpdated) {
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.onTokensUpdated = onTokensUpdated;
|
|
16
|
+
this.tokens = tokens;
|
|
17
|
+
this.expiresAt = expiresAt(tokens);
|
|
18
|
+
}
|
|
19
|
+
/** A valid access token. It refreshes the token pair first if the access token has expired. */
|
|
20
|
+
accessToken() {
|
|
21
|
+
if (this.expiresAt > Date.now() + REFRESH_MARGIN_MS) {
|
|
22
|
+
return Promise.resolve(this.tokens.access_token);
|
|
23
|
+
}
|
|
24
|
+
this.refreshing ??= this.refresh().finally(() => {
|
|
25
|
+
this.refreshing = undefined;
|
|
26
|
+
});
|
|
27
|
+
return this.refreshing;
|
|
28
|
+
}
|
|
29
|
+
/** The connections that the user enabled for the agent. */
|
|
30
|
+
connections() {
|
|
31
|
+
return this.send('GET', '/v1/connections');
|
|
32
|
+
}
|
|
33
|
+
/** Sends one call to the API with the agent access token. */
|
|
34
|
+
async send(method, pathAndQuery, body) {
|
|
35
|
+
const headers = {
|
|
36
|
+
authorization: `Bearer ${await this.accessToken()}`,
|
|
37
|
+
};
|
|
38
|
+
if (body !== undefined)
|
|
39
|
+
headers['content-type'] = 'application/json';
|
|
40
|
+
const response = await fetch(`${this.client.apiUrl}${pathAndQuery}`, {
|
|
41
|
+
method,
|
|
42
|
+
headers,
|
|
43
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
44
|
+
});
|
|
45
|
+
if (response.ok)
|
|
46
|
+
return (await response.json());
|
|
47
|
+
throw errorFromResponse(response.status, await response.text());
|
|
48
|
+
}
|
|
49
|
+
async refresh() {
|
|
50
|
+
const response = await this.client.oauth.refresh(this.tokens.refresh_token);
|
|
51
|
+
this.tokens = response;
|
|
52
|
+
this.expiresAt = expiresAt(response);
|
|
53
|
+
this.onTokensUpdated?.(response);
|
|
54
|
+
return response.access_token;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function expiresAt(tokens) {
|
|
58
|
+
return Date.now() + tokens.expires_in * 1000;
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubblebridge/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The BubbleBridge SDK for agent builders",
|
|
5
|
+
"keywords": ["bubblebridge", "oauth", "gmail", "google-calendar", "agent"],
|
|
6
|
+
"homepage": "https://bubblebridge.io",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bekzodaka/bubble-bridge.git",
|
|
10
|
+
"directory": "sdk-ts"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": ["dist"],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"lint": "eslint src",
|
|
33
|
+
"generate": "openapi-typescript ../openapi.json -o src/generated.ts",
|
|
34
|
+
"prepublishOnly": "npm run lint && npm run build"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@eslint/js": "^9",
|
|
38
|
+
"eslint": "^9",
|
|
39
|
+
"openapi-typescript": "^7",
|
|
40
|
+
"typescript": "^5",
|
|
41
|
+
"typescript-eslint": "^8"
|
|
42
|
+
}
|
|
43
|
+
}
|