diff --git a/packages/sdk/package-lock.json b/packages/sdk/package-lock.json index b8bf291230a60..3a610b100520a 100644 --- a/packages/sdk/package-lock.json +++ b/packages/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pipedream/sdk", - "version": "0.0.1", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pipedream/sdk", - "version": "0.0.1", + "version": "0.1.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@types/node": "^20.14.9", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index f8b8b75ec5e01..9a9979a5dbf02 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/sdk", - "version": "0.0.13", + "version": "0.1.0", "description": "Pipedream SDK", "type": "module", "main": "dist/server/index.js", @@ -40,6 +40,10 @@ ], "devDependencies": { "@types/node": "^20.14.9", + "@types/simple-oauth2": "^5.0.7", "typescript": "^5.5.2" + }, + "dependencies": { + "simple-oauth2": "^5.1.0" } } diff --git a/packages/sdk/src/server/index.ts b/packages/sdk/src/server/index.ts index 7f7e879f0e0c6..bf9a6f9521a84 100644 --- a/packages/sdk/src/server/index.ts +++ b/packages/sdk/src/server/index.ts @@ -2,6 +2,11 @@ // Pipedream project's public and secret keys and access customer credentials. // See the browser/ directory for the browser client. +import { + AccessToken, + ClientCredentials, +} from "simple-oauth2"; + /** * Options for creating a server-side client. * This is used to configure the ServerClient instance. @@ -22,6 +27,16 @@ export type CreateServerClientOpts = { */ secretKey: string; + /** + * The client ID of your workspace's OAuth application. + */ + oauthClientId?: string; + + /** + * The client secret of your workspace's OAuth application. + */ + oauthClientSecret?: string; + /** * The API host URL. Used by Pipedream employees. Defaults to "api.pipedream.com" if not provided. */ @@ -231,9 +246,9 @@ export type ErrorResponse = { export type ConnectAPIResponse = T | ErrorResponse; /** - * Options for making a request to the Connect API. + * Options for making a request to the Pipedream API. */ -interface ConnectRequestOptions extends Omit { +interface RequestOptions extends Omit { /** * Query parameters to include in the request URL. */ @@ -269,6 +284,8 @@ class ServerClient { environment?: string; secretKey: string; publicKey: string; + oauthClient: ClientCredentials; + oauthToken?: AccessToken; baseURL: string; /** @@ -283,20 +300,54 @@ class ServerClient { const { apiHost = "api.pipedream.com" } = opts; this.baseURL = `https://${apiHost}/v1`; + + this._configureOauthClient(opts, this.baseURL); + } + + private _configureOauthClient( + { + oauthClientId: id, + oauthClientSecret: secret, + }: CreateServerClientOpts, + tokenHost: string, + ) { + if (!id || !secret) { + return; + } + + this.oauthClient = new ClientCredentials({ + client: { + id, + secret, + }, + auth: { + tokenHost, + tokenPath: "/v1/oauth/token", + }, + }); } /** - * Generates an Authorization header using the public and secret keys. + * Generates an Authorization header for Connect using the public and secret + * keys of the target project. * * @returns The authorization header as a string. */ - private _authorizationHeader(): string { + private _connectAuthorizationHeader(): string { const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString("base64"); return `Basic ${encoded}`; } + async _oauthAuthorizationHeader(): Promise { + if (!this.oauthToken || this.oauthToken.expired()) { + this.oauthToken = await this.oauthClient.getToken({}); + } + + return `Bearer ${this.oauthToken.token.access_token}`; + } + /** - * Makes a request to the Connect API. + * Makes an HTTP request * * @template T - The expected response type. * @param path - The API endpoint path. @@ -304,9 +355,9 @@ class ServerClient { * @returns A promise resolving to the API response. * @throws Will throw an error if the response status is not OK. */ - async _makeConnectRequest( + async _makeRequest( path: string, - opts: ConnectRequestOptions = {}, + opts: RequestOptions = {}, ): Promise { const { params, @@ -315,7 +366,7 @@ class ServerClient { method = "GET", ...fetchOpts } = opts; - const url = new URL(`${this.baseURL}/connect${path}`); + const url = new URL(`${this.baseURL}${path}`); if (params) { Object.entries(params).forEach(([ @@ -329,7 +380,6 @@ class ServerClient { } const headers = { - "Authorization": this._authorizationHeader(), "Content-Type": "application/json", ...customHeaders, }; @@ -358,6 +408,52 @@ class ServerClient { return result; } + /** + * Makes a request to the Pipedream API. + * + * @template T - The expected response type. + * @param path - The API endpoint path. + * @param opts - The options for the request. + * @returns A promise resolving to the API response. + * @throws Will throw an error if the response status is not OK. + */ + async _makeApiRequest( + path: string, + opts: RequestOptions = {}, + ): Promise { + const headers = { + ...opts.headers ?? {}, + "Authorization": await this._oauthAuthorizationHeader(), + }; + return this._makeRequest(path, { + headers, + ...opts, + }); + } + + /** + * Makes a request to the Connect API. + * + * @template T - The expected response type. + * @param path - The API endpoint path. + * @param opts - The options for the request. + * @returns A promise resolving to the API response. + * @throws Will throw an error if the response status is not OK. + */ + async _makeConnectRequest( + path: string, + opts: RequestOptions = {}, + ): Promise { + const headers = { + ...opts.headers ?? {}, + "Authorization": this._connectAuthorizationHeader(), + }; + return this._makeRequest(`/connect${path}`, { + headers, + ...opts, + }); + } + /** * Creates a new connect token. * diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6d11dd9a0fc7..1aa916cee0305 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11485,9 +11485,14 @@ importers: packages/sdk: specifiers: '@types/node': ^20.14.9 + '@types/simple-oauth2': ^5.0.7 + simple-oauth2: ^5.1.0 typescript: ^5.5.2 + dependencies: + simple-oauth2: 5.1.0 devDependencies: '@types/node': 20.16.1 + '@types/simple-oauth2': 5.0.7 typescript: 5.5.4 packages/snowflake-sdk: @@ -16534,6 +16539,20 @@ packages: yargs: 17.7.2 dev: false + /@hapi/boom/10.0.1: + resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==} + dependencies: + '@hapi/hoek': 11.0.4 + dev: false + + /@hapi/bourne/3.0.0: + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + dev: false + + /@hapi/hoek/11.0.4: + resolution: {integrity: sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ==} + dev: false + /@hapi/hoek/9.3.0: resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} dev: false @@ -16544,6 +16563,14 @@ packages: '@hapi/hoek': 9.3.0 dev: false + /@hapi/wreck/18.1.0: + resolution: {integrity: sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==} + dependencies: + '@hapi/boom': 10.0.1 + '@hapi/bourne': 3.0.0 + '@hapi/hoek': 11.0.4 + dev: false + /@huggingface/inference/1.8.0: resolution: {integrity: sha512-Dkh7PiyMf6TINRocQsdceiR5LcqJiUHgWjaBMRpCUOCbs+GZA122VH9q+wodoSptj6rIQf7wIwtDsof+/gd0WA==} engines: {node: '>=18'} @@ -21107,6 +21134,10 @@ packages: '@types/node': 20.16.1 dev: false + /@types/simple-oauth2/5.0.7: + resolution: {integrity: sha512-8JbWVJbiTSBQP/7eiyGKyXWAqp3dKQZpaA+pdW16FCi32ujkzRMG8JfjoAzdWt6W8U591ZNdHcPtP2D7ILTKuA==} + dev: true + /@types/source-map/0.5.7: resolution: {integrity: sha512-LrnsgZIfJaysFkv9rRJp4/uAyqw87oVed3s1hhF83nwbo9c7MG9g5DqR0seHP+lkX4ldmMrVolPjQSe2ZfD0yA==} deprecated: This is a stub types definition for source-map (https://github.com/mozilla/source-map). source-map provides its own type definitions, so you don't need @types/source-map installed! @@ -33039,6 +33070,17 @@ packages: resolution: {integrity: sha512-uEv/AFO0ADI7d99OHDmh1QfYzQk/izT1vCmu/riQfh7qjBVUUgRT87E5s5h7CxWCA/+YoZerykpEthzVrW3LIw==} dev: false + /simple-oauth2/5.1.0: + resolution: {integrity: sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw==} + dependencies: + '@hapi/hoek': 11.0.4 + '@hapi/wreck': 18.1.0 + debug: 4.3.6 + joi: 17.10.2 + transitivePeerDependencies: + - supports-color + dev: false + /simple-swizzle/0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: