@gnosticdev/highlevel-sdk
Version:
SDK for the HighLevel API
337 lines (329 loc) • 12 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/v2/oauth/impl.ts
var impl_exports = {};
__export(impl_exports, {
OauthClientImpl: () => OauthClientImpl
});
module.exports = __toCommonJS(impl_exports);
var import_openapi_fetch2 = __toESM(require("openapi-fetch"), 1);
// src/v2/client/default.ts
var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
// 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
var import_src = __toESM(require("openapi-fetch/src/index.js"), 1);
// 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 = (0, import_openapi_fetch2.default)({
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;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
OauthClientImpl
});