learnworlds-js
Version:
TypeScript SDK for LearnWorlds API with full type safety and comprehensive method coverage
363 lines (358 loc) • 11.6 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 __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/index.ts
var index_exports = {};
__export(index_exports, {
LearnWorldsClient: () => LearnWorldsClient,
OAuth2Client: () => OAuth2Client
});
module.exports = __toCommonJS(index_exports);
// src/client.ts
var import_axios2 = __toESM(require("axios"), 1);
// src/oauth.ts
var import_axios = __toESM(require("axios"), 1);
var OAuth2Client = class {
http;
config;
accessToken;
refreshToken;
tokenExpiresAt;
constructor(config) {
this.config = config;
this.accessToken = config.accessToken;
this.refreshToken = config.refreshToken;
const baseURL = `https://${config.schoolDomain}.learnworlds.com`;
this.http = import_axios.default.create({
baseURL,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
}
/**
* Get the authorization URL for the authorization code grant flow
*/
getAuthorizationUrl(scope = "read_user_profile", state) {
const params = new URLSearchParams({
client_id: this.config.clientId,
redirect_uri: this.config.redirectUri || "",
response_type: "code",
scope
});
if (state) {
params.append("state", state);
}
return `https://${this.config.schoolDomain}.learnworlds.com/oauth2/authorize?${params.toString()}`;
}
/**
* Exchange authorization code for access token
*/
async exchangeAuthorizationCode(request) {
const params = new URLSearchParams({
grant_type: "authorization_code",
code: request.code,
redirect_uri: request.redirectUri,
client_id: this.config.clientId,
client_secret: this.config.clientSecret
});
const response = await this.http.post("/oauth2/token", params.toString());
await this.handleTokenResponse(response.data);
return response.data;
}
/**
* Get access token using resource owner password credentials grant
*/
async authenticateWithPassword(request) {
const params = new URLSearchParams({
grant_type: "password",
username: request.username,
password: request.password,
client_id: this.config.clientId,
client_secret: this.config.clientSecret
});
if (request.scope) {
params.append("scope", request.scope);
}
const response = await this.http.post("/oauth2/token", params.toString());
await this.handleTokenResponse(response.data);
return response.data;
}
/**
* Get access token using client credentials grant
* This is typically used for server-to-server authentication
*/
async authenticateWithClientCredentials(scope) {
const params = new URLSearchParams({
grant_type: "client_credentials",
client_id: this.config.clientId,
client_secret: this.config.clientSecret
});
if (scope) {
params.append("scope", scope);
}
const response = await this.http.post("/oauth2/token", params.toString());
await this.handleTokenResponse(response.data);
return response.data;
}
/**
* Refresh the access token using the refresh token
*/
async refreshAccessToken() {
if (!this.refreshToken) {
throw new Error("No refresh token available");
}
const params = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: this.refreshToken,
client_id: this.config.clientId,
client_secret: this.config.clientSecret
});
const response = await this.http.post("/oauth2/token", params.toString());
await this.handleTokenResponse(response.data);
return response.data;
}
/**
* Revoke the access token
*/
async revokeToken(token) {
const tokenToRevoke = token || this.accessToken;
if (!tokenToRevoke) {
throw new Error("No token to revoke");
}
const params = new URLSearchParams({
token: tokenToRevoke,
client_id: this.config.clientId,
client_secret: this.config.clientSecret
});
await this.http.post("/oauth2/revoke", params.toString());
if (!token || token === this.accessToken) {
this.accessToken = void 0;
this.tokenExpiresAt = void 0;
}
}
/**
* Get the current access token, refreshing if necessary
*/
async getAccessToken() {
if (!this.accessToken) {
throw new Error("No access token available. Please authenticate first.");
}
if (this.tokenExpiresAt && this.refreshToken) {
const now = /* @__PURE__ */ new Date();
const bufferTime = 5 * 60 * 1e3;
if (now.getTime() >= this.tokenExpiresAt.getTime() - bufferTime) {
await this.refreshAccessToken();
}
}
return this.accessToken;
}
/**
* Check if authenticated
*/
isAuthenticated() {
return !!this.accessToken;
}
/**
* Set tokens manually (useful for restoring session)
*/
setTokens(tokens) {
this.accessToken = tokens.accessToken;
this.refreshToken = tokens.refreshToken;
this.tokenExpiresAt = tokens.expiresAt;
}
/**
* Get current tokens (useful for persisting session)
*/
getTokens() {
return {
accessToken: this.accessToken,
refreshToken: this.refreshToken,
expiresAt: this.tokenExpiresAt
};
}
async handleTokenResponse(response) {
this.accessToken = response.access_token;
if (response.refresh_token) {
this.refreshToken = response.refresh_token;
}
if (response.expires_in) {
const expiresAt = /* @__PURE__ */ new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + response.expires_in);
this.tokenExpiresAt = expiresAt;
}
if (this.config.onTokenRefresh) {
await this.config.onTokenRefresh(response);
}
}
};
// src/client.ts
var LearnWorldsClient = class {
http;
oauth;
constructor(config) {
this.oauth = new OAuth2Client(config);
const baseURL = `https://${config.apiHost}/v2`;
this.http = import_axios2.default.create({
baseURL,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
this.http.interceptors.request.use(
async (config2) => {
try {
const token = await this.oauth.getAccessToken();
config2.headers.Authorization = `Bearer ${token}`;
} catch (error) {
}
return config2;
},
(error) => Promise.reject(error)
);
this.http.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry && this.oauth.getTokens().refreshToken) {
originalRequest._retry = true;
try {
await this.oauth.refreshAccessToken();
const token = await this.oauth.getAccessToken();
originalRequest.headers = originalRequest.headers || {};
originalRequest.headers.Authorization = `Bearer ${token}`;
return this.http(originalRequest);
} catch (refreshError) {
return Promise.reject(this.handleError(error));
}
}
return Promise.reject(this.handleError(error));
}
);
}
/**
* Get the OAuth2 client for authentication operations
*/
get auth() {
return this.oauth;
}
handleError(error) {
const lwError = new Error();
lwError.name = "LearnWorldsError";
if (error.response) {
lwError.status = error.response.status;
lwError.message = error.response.data?.message || error.message || "API Error";
lwError.details = error.response.data;
switch (error.response.status) {
case 401:
lwError.code = "UNAUTHORIZED";
lwError.message = "Invalid API key or authentication failed";
break;
case 403:
lwError.code = "FORBIDDEN";
lwError.message = "Access denied";
break;
case 404:
lwError.code = "NOT_FOUND";
lwError.message = "Resource not found";
break;
case 422:
lwError.code = "VALIDATION_ERROR";
lwError.message = "Validation failed";
break;
case 429:
lwError.code = "RATE_LIMIT";
lwError.message = "Rate limit exceeded";
break;
default:
lwError.code = "API_ERROR";
}
} else if (error.request) {
lwError.code = "NETWORK_ERROR";
lwError.message = "Network error - unable to reach API";
} else {
lwError.code = "UNKNOWN_ERROR";
lwError.message = error.message || "Unknown error occurred";
}
return lwError;
}
async makeRequest(method, endpoint, data, params) {
const response = await this.http.request({
method,
url: endpoint,
data,
params
});
if (!response.data.success) {
const error = new Error(response.data.message || "API request failed");
error.code = "API_ERROR";
error.details = response.data.errors;
throw error;
}
return response.data.data;
}
async getAllCourses(params) {
return this.makeRequest("GET", "/courses", void 0, params);
}
async getAllBundles(params) {
return this.makeRequest("GET", "/bundles", void 0, params);
}
async createUser(userData) {
return this.makeRequest("POST", "/users", userData);
}
async updateUser(userId, userData) {
return this.makeRequest("PUT", `/users/${userId}`, userData);
}
async updateUserTags(userId, tagData) {
return this.makeRequest("PATCH", `/users/${userId}/tags`, tagData);
}
async enrollUserToProduct(enrollmentData) {
return this.makeRequest("POST", "/enrollments", enrollmentData);
}
async unenrollUserFromProduct(unenrollmentData) {
await this.makeRequest("DELETE", "/enrollments", unenrollmentData);
}
async getUser(userId) {
return this.makeRequest("GET", `/users/${userId}`);
}
async getCourse(courseId) {
return this.makeRequest("GET", `/courses/${courseId}`);
}
async getBundle(bundleId) {
return this.makeRequest("GET", `/bundles/${bundleId}`);
}
async getUserEnrollments(userId) {
return this.makeRequest("GET", `/users/${userId}/enrollments`);
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
LearnWorldsClient,
OAuth2Client
});