diff --git a/packages/connect-react/examples/nextjs/package-lock.json b/packages/connect-react/examples/nextjs/package-lock.json index 0351814ee6464..c56ed62c8171b 100644 --- a/packages/connect-react/examples/nextjs/package-lock.json +++ b/packages/connect-react/examples/nextjs/package-lock.json @@ -23,7 +23,7 @@ }, "../..": { "name": "@pipedream/connect-react", - "version": "1.0.0-preview.8", + "version": "1.0.0-preview.9", "license": "MIT", "dependencies": { "@pipedream/sdk": "workspace:^", diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 6512e3d698115..75bb5d23e1aa5 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -1,6 +1,18 @@ # Changelog +## [1.1.1] - 2024-12-011 + +### Changed + +- Remove deprecated asynchoronous response handling code. + +## [1.1.0] - 2024-12-010 + +### Changed + +- Naming and docs improvements + ## [1.0.12] - 2024-12-06 ### Added diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 37cca219591be..43fd7c74c3430 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/sdk", - "version": "1.1.0", + "version": "1.1.1", "description": "Pipedream SDK", "main": "dist/server/server/index.js", "module": "dist/server/server/index.js", diff --git a/packages/sdk/src/browser/async.ts b/packages/sdk/src/browser/async.ts deleted file mode 100644 index 0cff886578f93..0000000000000 --- a/packages/sdk/src/browser/async.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AsyncResponseManager } from "../shared/async.js"; -import type { AsyncResponseManagerOpts } from "../shared/async.js"; - -export type BrowserAsyncResponseManagerOpts = { - apiHost: string; - getConnectToken: () => Promise; -}; - -export class BrowserAsyncResponseManager extends AsyncResponseManager { - private browserOpts: BrowserAsyncResponseManagerOpts; - - constructor(opts: BrowserAsyncResponseManagerOpts) { - super(); - this.browserOpts = opts; - } - - protected override async getOpts(): Promise { - const token = await this.browserOpts.getConnectToken(); - const url = `wss://${this.browserOpts.apiHost}/websocket?ctok=${token}`; - return { - url, - }; - } -} diff --git a/packages/sdk/src/browser/index.ts b/packages/sdk/src/browser/index.ts index 94d886e5cc929..e038701c41874 100644 --- a/packages/sdk/src/browser/index.ts +++ b/packages/sdk/src/browser/index.ts @@ -4,7 +4,6 @@ // operations, like connecting accounts via Pipedream Connect. See the server/ // directory for the server client. -import { BrowserAsyncResponseManager } from "./async.js"; import { AccountsRequestResponse, BaseClient, @@ -133,7 +132,6 @@ export function createFrontendClient(opts: CreateBrowserClientOpts = {}) { * A client for interacting with the Pipedream Connect API from the browser. */ export class BrowserClient extends BaseClient { - protected override asyncResponseManager: BrowserAsyncResponseManager; private baseURL: string; private iframeURL: string; private iframe?: HTMLIFrameElement; @@ -155,10 +153,6 @@ export class BrowserClient extends BaseClient { this.iframeURL = `${this.baseURL}/_static/connect.html`; this.tokenCallback = opts.tokenCallback; this.externalUserId = opts.externalUserId; - this.asyncResponseManager = new BrowserAsyncResponseManager({ - apiHost: this.apiHost, - getConnectToken: () => this.token(), - }); } private async token() { diff --git a/packages/sdk/src/server/async.ts b/packages/sdk/src/server/async.ts deleted file mode 100644 index 560d1eef665f6..0000000000000 --- a/packages/sdk/src/server/async.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { AccessToken } from "simple-oauth2"; -import { AsyncResponseManager } from "../shared/async"; -import type { AsyncResponseManagerOpts } from "../shared/async"; -import { adapters } from "@rails/actioncable"; -import { WebSocket } from "ws"; - -declare global { - function addEventListener(type: string, listener: () => void): void; - function removeEventListener(type: string, listener: () => void): void; -} - -export type ServerAsyncResponseManagerOpts = { - apiHost: string; - getOauthToken: () => Promise | AccessToken; - getProjectId: () => Promise | string; -}; - -export class ServerAsyncResponseManager extends AsyncResponseManager { - private serverOpts: ServerAsyncResponseManagerOpts; - - constructor(opts: ServerAsyncResponseManagerOpts) { - super(); - this.serverOpts = opts; - // eslint-disable-next-line @typescript-eslint/no-empty-function - global.addEventListener = () => {}; - // eslint-disable-next-line @typescript-eslint/no-empty-function - global.removeEventListener = () => {}; - if (typeof adapters.WebSocket === "undefined") - adapters.WebSocket = WebSocket as unknown as typeof adapters.WebSocket; - } - - protected override async getOpts(): Promise { - const oauthToken = await this.serverOpts.getOauthToken(); - if (!oauthToken?.token?.access_token) { - throw new Error("Invalid OAuth token structure"); - } - const token = oauthToken.token.access_token; - const projectId = await this.serverOpts.getProjectId(); - return { - url: `wss://${this.serverOpts.apiHost}/websocket?oauth_token=${token}`, - subscriptionParams: { - project_id: projectId, - }, - }; - } -} diff --git a/packages/sdk/src/server/index.ts b/packages/sdk/src/server/index.ts index aa135befc4849..c2776294302f0 100644 --- a/packages/sdk/src/server/index.ts +++ b/packages/sdk/src/server/index.ts @@ -8,7 +8,6 @@ import { import { Account, BaseClient, type AppInfo, type ConnectTokenResponse, } from "../shared"; -import { ServerAsyncResponseManager } from "./async"; export * from "../shared"; /** @@ -137,7 +136,6 @@ export function createBackendClient(opts: BackendClientOpts) { * A client for interacting with the Pipedream Connect API on the server-side. */ export class BackendClient extends BaseClient { - protected override asyncResponseManager: ServerAsyncResponseManager; private oauthClient: ClientCredentials; private oauthToken?: AccessToken; protected projectId: string; @@ -155,18 +153,6 @@ export class BackendClient extends BaseClient { this.projectId = opts.projectId; this.oauthClient = this.newOauthClient(opts.credentials, this.baseApiUrl); - this.asyncResponseManager = new ServerAsyncResponseManager({ - apiHost: this.apiHost, - getOauthToken: async () => { - await this.ensureValidOauthToken(); - return this.oauthToken as AccessToken; - }, - getProjectId: () => { - if (!this.projectId) - throw "Attempted to connect to websocket without a valid Project id"; - return this.projectId; - }, - }); } private ensureValidEnvironment(environment?: string) { diff --git a/packages/sdk/src/shared/async.ts b/packages/sdk/src/shared/async.ts deleted file mode 100644 index 6cb3a8b636fde..0000000000000 --- a/packages/sdk/src/shared/async.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { createConsumer } from "@rails/actioncable"; -import type { - Consumer, Subscription, -} from "@rails/actioncable"; - -/** - * A generic API response that returns asynchronously. - * See AsyncResponseManager for details. - */ -export type AsyncResponse = { - async_handle: string; -}; - -export type AsyncErrorResponse = { - errors: string[]; -}; - -export type AsyncResponseManagerOpts = { - url: string; - subscriptionParams?: Record; -}; - -type Handle = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - resolve: (value: any) => void; - reject: (reason: string) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - promise: Promise; -}; - -const createHandle = (): Handle => { - const handle: Partial = {}; - handle.promise = new Promise((resolve, reject) => { - handle.resolve = resolve; - handle.reject = reject; - }); - return handle as Handle; -}; - -function randomString(n: number) { - const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - return Array(n).fill(0) - .map(() => alphabet[Math.floor(Math.random() * alphabet.length)]) - .join(""); -} - -export abstract class AsyncResponseManager { - protected cable?: Consumer; - protected handles: Record = {}; - protected subscription?: Subscription; - protected opts?: AsyncResponseManagerOpts; - - async connect() { - this.createCable(); - await this.createSubscription(); - } - - createAsyncHandle() { - const asyncHandle = randomString(12); - this.handles[asyncHandle] = createHandle(); - return asyncHandle; - } - - protected createCable(): Consumer { - if (!this.opts?.url) throw "Missing ActionCable url"; - this.cable = createConsumer(this.opts.url); - this.cable.ensureActiveConnection(); - return this.cable; - } - - protected async createSubscription(): Promise { - this.subscription = await new Promise((resolve, reject) => { - this.subscription = this.cable?.subscriptions?.create({ - channel: "AsyncResponseChannel", - ...(this.opts?.subscriptionParams ?? {}), - }, { - connected: () => resolve(this.subscription as Subscription), - rejected: (reason?: string) => reject(reason), - received: (d: { asyncHandle: string; }) => { - const handle = this.handles[d.asyncHandle]; - if (handle) { - handle.resolve(d); - setTimeout(() => delete this.handles[d.asyncHandle], 60000); - } - }, - disconnected: (opts?: { willAttemptReconnect: boolean; }) => { - if (!opts?.willAttemptReconnect) { - for (const asyncHandle of Object.keys(this.handles)) { - const handle: Handle = this.handles[asyncHandle]; - handle.reject("AsyncResponseChannel disconnected"); - } - this.handles = {}; - } - }, - }); - }); - return this.subscription; - } - - async ensureConnected() { - this.cable?.ensureActiveConnection(); - const _opts = await this.getOpts(); - if (!this.opts || JSON.stringify(_opts) !== JSON.stringify(this.opts) || !this.cable?.connection.isOpen()) { - this.opts = _opts; - await this.connect(); - } - } - - protected abstract getOpts(): Promise; - - async waitFor(asyncHandle: string): Promise { - await this.connect(); - const handle = this.handles[asyncHandle] ?? createHandle(); - this.handles[asyncHandle] = handle; - return handle.promise; - } -} - diff --git a/packages/sdk/src/shared/index.ts b/packages/sdk/src/shared/index.ts index da8fce0b9bc45..3ade24b3ad51f 100644 --- a/packages/sdk/src/shared/index.ts +++ b/packages/sdk/src/shared/index.ts @@ -1,9 +1,4 @@ // This code is meant to be shared between the browser and server. -import type { - AsyncResponse, - AsyncErrorResponse, - AsyncResponseManager, -} from "./async.js"; import type { ConfigurableProps, ConfiguredProps, @@ -580,7 +575,6 @@ export interface AsyncRequestOptions extends RequestOptions { export abstract class BaseClient { version = sdkVersion; protected apiHost: string; - protected abstract asyncResponseManager: AsyncResponseManager; protected readonly baseApiUrl: string; protected environment: string; protected projectId?: string; @@ -744,35 +738,6 @@ export abstract class BaseClient { return this.makeAuthorizedRequest(fullPath, opts); } - /** - * Makes a request to the Connect API using Connect authorization. - * This version makes an asynchronous request, fulfilled via Websocket. - * - * @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. - */ - protected async makeConnectRequestAsync( - path: string, - opts: AsyncRequestOptions, - ): Promise { - await this.asyncResponseManager.ensureConnected(); - const data = await this.makeConnectRequest< - AsyncResponse | AsyncErrorResponse | T - >(path, opts); - if ("errors" in data && data.errors.length) { - throw new Error(data.errors[0]); - } - if ("async_handle" in data && data.async_handle) { - const result = await this.asyncResponseManager.waitFor( - data.async_handle, - ); - return result; - } - return data as T; - } - /** * Retrieves the list of accounts associated with the project. * @@ -956,14 +921,13 @@ export abstract class BaseClient { : componentId; const body = { - async_handle: this.asyncResponseManager.createAsyncHandle(), external_user_id: externalUserId, id, prop_name: opts.propName, configured_props: opts.configuredProps, dynamic_props_id: opts.dynamicPropsId, }; - return this.makeConnectRequestAsync("/components/configure", { + return this.makeConnectRequest("/components/configure", { method: "POST", body, }); @@ -1013,14 +977,13 @@ export abstract class BaseClient { // RpcActionReloadPropsInput const body = { - async_handle: this.asyncResponseManager.createAsyncHandle(), external_user_id: externalUserId, id, configured_props: opts.configuredProps, dynamic_props_id: opts.dynamicPropsId, }; - return this.makeConnectRequestAsync>( + return this.makeConnectRequest>( "/components/props", { // TODO trigger method: "POST", @@ -1072,13 +1035,12 @@ export abstract class BaseClient { : actionId; const body = { - async_handle: this.asyncResponseManager.createAsyncHandle(), external_user_id: externalUserId, id, configured_props: opts.configuredProps, dynamic_props_id: opts.dynamicPropsId, }; - return this.makeConnectRequestAsync("/actions/run", { + return this.makeConnectRequest("/actions/run", { method: "POST", body, }); @@ -1126,14 +1088,13 @@ export abstract class BaseClient { : triggerId; const body = { - async_handle: this.asyncResponseManager.createAsyncHandle(), external_user_id: externalUserId, id, configured_props: opts.configuredProps, dynamic_props_id: opts.dynamicPropsId, webhook_url: opts.webhookUrl, }; - return this.makeConnectRequestAsync("/triggers/deploy", { + return this.makeConnectRequest("/triggers/deploy", { method: "POST", body, });