UNPKG

@gnosticdev/highlevel-sdk

Version:
304 lines (296 loc) 10.3 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/v2/oauth/impl.ts import createClient3 from "openapi-fetch"; // src/v2/client/default.ts import createClient2 from "openapi-fetch"; // src/lib/errors.ts var HighLevelSDKErrorCodes = { INVALID_AUTH_HEADER: "INVALID_AUTH_HEADER", INVALID_AUTH_TYPE: "INVALID_AUTH_TYPE", NO_HANDLER_REGISTERED: "NO_HANDLER_REGISTERED", INVALID_WEBHOOK_PAYLOAD: "INVALID_WEBHOOK_PAYLOAD", API_KEY_REQUIRED: "API_KEY_REQUIRED" }; var ERROR_MESSAGES = { [HighLevelSDKErrorCodes.INVALID_AUTH_HEADER]: 'Authorization header must start with "Bearer "', [HighLevelSDKErrorCodes.INVALID_AUTH_TYPE]: "Invalid authentication type provided", [HighLevelSDKErrorCodes.NO_HANDLER_REGISTERED]: "No handler registered for webhook event: {event}", [HighLevelSDKErrorCodes.INVALID_WEBHOOK_PAYLOAD]: "Invalid webhook payload", [HighLevelSDKErrorCodes.API_KEY_REQUIRED]: "apiKey is required" }; // src/v2/client/with-oauth.ts var DEFAULT_BASE_AUTH_URL = "https://marketplace.leadconnectorhq.com/oauth/chooselocation"; // src/v2/client/types.ts import createClient from "openapi-fetch/src/index.js"; // src/v2/client/default.ts var DEFAULT_V2_BASE_URL = "https://services.leadconnectorhq.com"; // src/v2/oauth/impl.ts var OauthClientImpl = class { static { __name(this, "OauthClientImpl"); } _accessToken; _refreshToken; /** * Type of user that is accessing the API. * * Only used for generating tokens, similar to `accessType` but uses `Location` (Sub-Account) or `Company` (Agency) */ userType; /** * Underlying oauth client created by `createClient` method from `openapi-fetch` * * Most likely do not need to use this unless special use case. */ _client; // avoid conflict with the `expiresAt` getter _expiresAt; scopes; config; baseUrl; baseOauthUrl; // avoid conflict with the `tokenData` property tokenData; storeTokenFn; /** * creates a new oauth client for use with the HighLevel API * @constructor * @param config - configuration for your app */ constructor(config) { this.config = config; this.baseUrl = config.baseUrl ?? DEFAULT_V2_BASE_URL; this.baseOauthUrl = config.baseAuthUrl ?? DEFAULT_BASE_AUTH_URL; this.scopes = Array.isArray(config.scopes) ? config.scopes.join(" ") : config.scopes; if (this.scopes.length === 0) { console.warn( "No scopes provided! Pass the scopes set in your app to the scopes param" ); } this.userType = config.accessType === "Sub-Account" ? "Location" : "Company"; this.storeTokenFn = config.storageFunction ?? (() => { if (!this.tokenData) { throw new Error("No token data to store"); } return Promise.resolve(this.tokenData); }); this._client = createClient3({ baseUrl: this.baseUrl }); } /** * The time (in seconds) when the access token expires. */ get expiresAt() { return this._expiresAt; } /** * Update the stored token data with the provided token data. * @param updatedTokenData - The token data to update. */ updateTokenData(updatedTokenData) { this.tokenData = { ...this.tokenData, ...updatedTokenData }; this.storeTokenFn(this.tokenData); } /** * generates the authorization url for your app using the baseAuthUrl, clientId, redirectUri, and scopes. * @see https://highlevel.stoplight.io/docs/integrations/a04191c0fabf9-authorization * @example * ```ts https://marketplace.leadconnectorhq.com/oauth/chooselocation?response_type=code&redirect_uri=https://myapp.com/oauth/callback/gohighlevel&client_id=CLIENT_ID&scope=conversations/message.readonly conversations/message.write * ``` */ getAuthorizationUrl() { const url = new URL(this.baseOauthUrl); const requiredParams = { client_id: this.config.clientId, redirect_uri: this.config.redirectUri, scope: this.scopes, response_type: "code" }; const searchParams = new URLSearchParams(requiredParams); url.search = searchParams.toString(); return url.toString(); } /** * Stores everything from the Token response including the accessToken, refreshToken, locationID, and adds the expiresAt time (in ms). * **NOTE**: You can add a `storageFunction` to the config to store the token data in your database or cache. * @param tokenData - the token response from the server * @returns the token data with the `expiresAt` time added */ async storeTokenData(tokenData) { this._accessToken = tokenData.access_token; this._refreshToken = tokenData.refresh_token; const expiresAt = Date.now() + tokenData.expires_in * 1e3; this.tokenData = { ...tokenData, expiresAt }; return this.storeTokenFn(this.tokenData); } /** * Check if the token is expired or if we need to refresh it * @returns true if the token is expired or if we need to refresh it */ isTokenExpired() { if (!this.tokenData?.expiresAt || !this._refreshToken) return true; return this.tokenData.expiresAt <= Date.now() + 5 * 60 * 1e3; } /** * Returns a valid access token by either refreshing the token or exchanging the auth code for a new token. * * Also stores the token via the `storeTokenData` if provided. * @param authCode - The authorization code received from the OAuth provider. * @throws {Error} - If no token response is received or if no auth code or refresh token is provided. * @returns The access token. */ async getAccessToken(authCode) { if (this._accessToken && !this.isTokenExpired()) { return this._accessToken; } if (authCode) { const tokenResponse2 = await this.exchangeToken(authCode); if (!tokenResponse2) throw new Error("Error in token exchange"); const storedToken2 = await this.storeTokenData(tokenResponse2); return storedToken2.access_token; } if (!this._refreshToken) { return null; } const tokenResponse = await this.refreshAccessToken(); const storedToken = await this.storeTokenData(tokenResponse); return storedToken.access_token; } /** * Generates a new access token using the provided authorization code. \ * **NOTE**: This method does not save the token data to the client. * To store the token: * - `storeTokenData` - saves the token data. * - `getAccessToken` - fetches and stores the token data. * @param authCode - Parameters required to generate a new token. * @returns The token response from the server. */ async exchangeToken(authCode) { const tokenParams = { client_id: this.config.clientId, client_secret: this.config.clientSecret, grant_type: "authorization_code", user_type: this.userType, code: authCode }; const { data, error } = await this.#fetchAccessToken(tokenParams); if (error) { console.error("Error: exchanging token", error); throw new Error(error.message?.toString() ?? String(error)); } return data; } /** * Refreshes the current access token. * **NOTE**: This method does not save the token data to the client. * To store the token: * - `storeTokenData` - saves the token data. * - `getAccessToken` - fetches and stores the token data. * @returns The token response from the server. */ async refreshAccessToken() { if (!this._refreshToken) { throw new Error("No refresh token available."); } const tokenParams = { client_id: this.config.clientId, client_secret: this.config.clientSecret, refresh_token: this._refreshToken, grant_type: "refresh_token", user_type: this.userType }; const { data, error } = await this.#fetchAccessToken(tokenParams); if (error) { console.error("Error: refreshing token", error); throw new Error(error.message?.toString() ?? String(error)); } return data; } /** * Main method for getting getting a token from HighLevel v2 API. * * Sends a `POST` request to the `/oauth/token` endpoint. * * @param tokenParams - Auth code params or refresh token params * @private */ async #fetchAccessToken(tokenParams) { return this._client.POST("/oauth/token", { body: tokenParams, headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" }, bodySerializer(_body) { return new URLSearchParams(_body).toString(); } }); } /** * generate a location AccessToken from Agency AccessToken * @param companyId - your agency id * @param locationId - The locationId is the locationId of the location you want to get a token for */ async generateLocationToken({ companyId, locationId }) { const { data, error, response } = await this._client.POST( "/oauth/locationToken", { body: { companyId, locationId }, params: { header: { Version: "2021-07-28" } }, bodySerializer(_body) { return new URLSearchParams(_body).toString(); }, headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" } } ); if (error) { console.error(await response.text()); throw new Error(error.message?.toString() ?? String(error)); } return data; } /** * Get all locations under your agency that have installed your app * @param query - search for installed locations using any of these properties * @param appId - the appId of your app * @param companyId - the companyId of your agency */ async getInstalledLocations(query) { const { data, error } = await this._client.GET( "/oauth/installedLocations", { params: { query: { ...query, appId: query.appId, companyId: query.companyId }, header: { Version: "2021-07-28" } } } ); if (error) { console.error("Error: installed locations", error); throw new Error(error.message?.toString()); } return data; } }; export { OauthClientImpl };