Skip to content

Commit a257036

Browse files
committed
feat(sdk-package): call apis directly from client sdk
1 parent 8b37319 commit a257036

File tree

7 files changed

+932
-638
lines changed

7 files changed

+932
-638
lines changed

packages/sdk/package-lock.json

Lines changed: 13 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"devDependencies": {
3939
"@types/fetch-mock": "^7.3.8",
4040
"@types/jest": "^29.5.13",
41-
"@types/node": "^20.14.9",
41+
"@types/node": "^20.17.6",
4242
"@types/simple-oauth2": "^5.0.7",
4343
"jest": "^29.7.0",
4444
"jest-fetch-mock": "^3.0.3",

packages/sdk/src/browser/index.ts

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
// operations, like connecting accounts via Pipedream Connect. See the server/
55
// directory for the server client.
66

7+
import {
8+
BaseClient, ConnectTokenResponse,
9+
} from "../shared";
10+
711
/**
812
* Options for creating a browser-side client. This is used to configure the
913
* BrowserClient instance.
@@ -20,8 +24,31 @@ type CreateBrowserClientOpts = {
2024
* "pipedream.com" if not provided.
2125
*/
2226
frontendHost?: string;
27+
28+
/**
29+
* The API host URL. Used by Pipedream employees. Defaults to
30+
* "api.pipedream.com" if not provided.
31+
*/
32+
apiHost?: string;
33+
34+
/**
35+
* Will be called whenever we need a new token.
36+
*
37+
* The callback function should return the response from
38+
* `serverClient.createConnectToken`.
39+
*/
40+
tokenCallback?: TokenCallback;
41+
42+
/**
43+
* An external user ID associated with the token.
44+
*/
45+
externalUserId?: string;
2346
};
2447

48+
export type TokenCallback = (opts: {
49+
externalUserId: string;
50+
}) => Promise<ConnectTokenResponse>;
51+
2552
/**
2653
* The name slug for an app, a unique, human-readable identifier like "github"
2754
* or "google_sheets". Find this in the Authentication section for any app's
@@ -51,8 +78,10 @@ class ConnectError extends Error {}
5178
type StartConnectOpts = {
5279
/**
5380
* The token used for authenticating the connection.
81+
*
82+
* Optional if client already initialized with token
5483
*/
55-
token: string;
84+
token?: string;
5685

5786
/**
5887
* The app to connect to, either as an ID or an object containing the ID.
@@ -98,22 +127,48 @@ export function createFrontendClient(opts: CreateBrowserClientOpts = {}) {
98127
/**
99128
* A client for interacting with the Pipedream Connect API from the browser.
100129
*/
101-
class BrowserClient {
102-
private environment?: string;
130+
export class BrowserClient extends BaseClient {
103131
private baseURL: string;
104132
private iframeURL: string;
105133
private iframe?: HTMLIFrameElement;
106134
private iframeId = 0;
135+
private tokenCallback?: TokenCallback;
136+
private _token?: string;
137+
externalUserId?: string;
107138

108139
/**
109140
* Constructs a new `BrowserClient` instance.
110141
*
111142
* @param opts - The options for configuring the browser client.
112143
*/
113144
constructor(opts: CreateBrowserClientOpts) {
114-
this.environment = opts.environment;
145+
super(opts);
115146
this.baseURL = `https://${opts.frontendHost || "pipedream.com"}`;
116147
this.iframeURL = `${this.baseURL}/_static/connect.html`;
148+
this.tokenCallback = opts.tokenCallback;
149+
this.externalUserId = opts.externalUserId;
150+
}
151+
152+
private async token() {
153+
// TODO: handle token expiration
154+
if (this._token) {
155+
return this._token;
156+
}
157+
if (this.tokenCallback) {
158+
if (!this.externalUserId) {
159+
throw new Error("No external user ID provided");
160+
}
161+
const token = await this.tokenCallback({
162+
externalUserId: this.externalUserId,
163+
});
164+
this._token = token.token;
165+
return token.token;
166+
}
167+
throw new Error("No token provided");
168+
}
169+
170+
private refreshToken() {
171+
this._token = undefined;
117172
}
118173

119174
/**
@@ -161,6 +216,7 @@ class BrowserClient {
161216
} catch (err) {
162217
opts.onError?.(err as ConnectError);
163218
}
219+
this.refreshToken(); // token is used only once
164220
}
165221

166222
/**
@@ -182,9 +238,10 @@ class BrowserClient {
182238
*
183239
* @throws {ConnectError} If the app option is not a string.
184240
*/
185-
private createIframe(opts: StartConnectOpts) {
241+
private async createIframe(opts: StartConnectOpts) {
242+
const token = opts.token || (await this.token());
186243
const qp = new URLSearchParams({
187-
token: opts.token,
244+
token,
188245
});
189246

190247
if (this.environment) {
@@ -216,4 +273,11 @@ class BrowserClient {
216273

217274
document.body.appendChild(iframe);
218275
}
276+
277+
protected async authHeaders(): Promise<string> {
278+
if (!(await this.token())) {
279+
throw new Error("No token provided");
280+
}
281+
return `Bearer ${await this.token()}`;
282+
}
219283
}

0 commit comments

Comments
 (0)