UNPKG

learnworlds-js

Version:

TypeScript SDK for LearnWorlds API with full type safety and comprehensive method coverage

325 lines (322 loc) 9.86 kB
// src/client.ts import axios2 from "axios"; // src/oauth.ts import axios from "axios"; 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 = axios.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 = axios2.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`); } }; export { LearnWorldsClient, OAuth2Client };