@memberjunction/actions-bizapps-lms
Version:
LMS system integration actions for MemberJunction
204 lines • 8.69 kB
TypeScript
import { BaseLMSAction } from '../../base/base-lms.action.js';
import { UserInfo } from '@memberjunction/core';
import { ActionParam } from '@memberjunction/actions-base';
import { LWApiEnrollmentStatus, LWApiProgressData, LearnWorldsUser } from './interfaces/index.js';
/**
* Base class for all LearnWorlds LMS actions.
* Handles LearnWorlds-specific authentication and API interaction patterns.
*/
export declare abstract class LearnWorldsBaseAction extends BaseLMSAction {
protected lmsProvider: string;
protected integrationName: string;
/**
* LearnWorlds API version
*/
protected apiVersion: string;
/**
* Concurrency limit for parallel API calls to avoid overwhelming the API.
*/
protected static readonly CONCURRENCY_LIMIT = 5;
/**
* Allowed user roles in LearnWorlds.
*/
protected static readonly ALLOWED_ROLES: readonly string[];
/**
* Maximum number of items per page supported by the LearnWorlds API
*/
protected static readonly LW_MAX_PAGE_SIZE = 100;
private static readonly MAX_RETRIES;
private static readonly BASE_DELAY_MS;
private static readonly MAX_DELAY_MS;
private static readonly RATE_LIMIT_WINDOW_MS;
private static readonly RATE_LIMIT_MAX_REQUESTS;
private static readonly INTER_BATCH_DELAY_MS;
/**
* Safety limit for pagination to prevent infinite loops if the API misbehaves.
*/
protected static readonly MAX_PAGES = 100;
/**
* Sliding-window timestamps shared across all instances so concurrent
* actions against the same LearnWorlds school stay within the limit.
*/
private static requestTimestamps;
/**
* Reset the global rate-limiter state. Intended for test teardown only.
*/
static ResetRateLimiter(): void;
/**
* Current action parameters (set by the framework or by SetCompanyContext)
*/
protected params: ActionParam[];
/**
* Tracks the number of actual HTTP requests made since last reset.
* Useful for callers that need accurate API call counts (e.g., bulk data).
*/
protected apiCallCount: number;
/**
* Public accessor for the running API call count.
*/
get ApiCallCount(): number;
/**
* Set the company context for direct (non-framework) calls.
* This populates `this.params` so that `makeLearnWorldsRequest` can find the CompanyID.
*/
SetCompanyContext(companyId: string): void;
/**
* Makes an authenticated request to LearnWorlds API.
* The body parameter accepts any object that will be JSON-serialized.
*/
protected makeLearnWorldsRequest<T = Record<string, unknown>>(endpoint: string, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: object | null, contextUser?: UserInfo): Promise<T>;
/**
* Resolves integration credentials and builds the base URL and headers.
*/
private buildRequestConfig;
/**
* Makes an authenticated request to a non-versioned LearnWorlds API endpoint (e.g. /admin/api/sso).
*/
protected makeLearnWorldsNonVersionedRequest<T = Record<string, unknown>>(endpoint: string, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: object | null, contextUser?: UserInfo): Promise<T>;
/**
* Parses error details from a non-OK API response.
*/
private buildErrorMessage;
/**
* Makes a paginated request to LearnWorlds API
*/
protected makeLearnWorldsPaginatedRequest<T = Record<string, unknown>>(endpoint: string, queryParams?: Record<string, string | number | boolean>, contextUser?: UserInfo, maxResults?: number): Promise<T[]>;
/**
* Convert LearnWorlds date format to Date object
*/
protected parseLearnWorldsDate(dateString: string | number): Date;
/**
* Format date for LearnWorlds API (ISO 8601)
*/
protected formatLearnWorldsDate(date: Date): string;
/**
* Map LearnWorlds user status to standard status
*/
protected mapUserStatus(status: string): 'active' | 'inactive' | 'suspended';
/**
* Map LearnWorlds enrollment status
*/
protected mapLearnWorldsEnrollmentStatus(enrollment: LWApiEnrollmentStatus): 'active' | 'completed' | 'expired' | 'suspended';
/**
* Calculate progress from LearnWorlds data
*/
protected calculateProgress(progressData: LWApiProgressData): {
percentage: number;
completedUnits: number;
totalUnits: number;
timeSpent: number;
};
/**
* Safely parses a date string to ISO format.
* Returns undefined if the input is falsy or produces an invalid date.
*/
protected safeParseDateToISO(dateString: string | undefined): string | undefined;
/**
* Validates that a value is safe to use as a URL path segment.
* Rejects values containing path traversal or URL manipulation characters.
*/
protected validatePathSegment(value: string, paramName: string): string;
/**
* Validates that a school domain is safe to use in URL construction.
* Must look like a hostname (alphanumeric + hyphens + dots).
*/
private validateSchoolDomain;
/**
* Validates that a role is in the allowed set.
*/
protected validateRole(role: string): string;
/**
* Validates that a string is a plausible email address.
*/
protected validateEmail(email: string, paramName?: string): string;
/**
* Validates that a redirect URL is either a relative path or an absolute URL
* that belongs to the LearnWorlds school domain and uses http(s).
*/
protected validateRedirectTo(redirectTo: string, schoolDomain?: string): string;
/**
* Returns a promise that resolves after `ms` milliseconds.
*/
private waitForRetryDelay;
/**
* Pure calculation — determines how long to wait before the next retry.
* Prefers the Retry-After header (seconds → ms, capped); falls back to
* exponential backoff with random jitter.
*/
private calculateRetryDelay;
/**
* Proactive throttle: if the sliding window is at capacity, sleep until
* the oldest request falls outside the window.
*/
private waitForRateLimitCapacity;
/**
* Stamps the current time into the sliding window.
*/
private recordRequest;
/**
* Wraps `fetch` with 429-aware retry + proactive rate-limit throttling.
* Non-429 responses are returned immediately without retry.
* If all retries are exhausted the final 429 response is returned (not thrown).
*/
private sendRequestWithRetry;
/**
* Processes items in batches with controlled concurrency.
* Prevents unbounded parallel API calls from overwhelming the target API.
*/
protected processInBatches<TItem, TResult>(items: TItem[], processFn: (item: TItem) => Promise<TResult>, batchSize?: number): Promise<TResult[]>;
/**
* Gets a required string parameter, throwing if missing or empty.
*/
protected getRequiredStringParam(params: ActionParam[], name: string): string;
/**
* Gets an optional string parameter.
*/
protected getOptionalStringParam(params: ActionParam[], name: string): string | undefined;
/**
* Gets an optional boolean parameter with a default value.
* When defaultValue is undefined, returns undefined if the parameter is missing.
*/
protected getOptionalBooleanParam(params: ActionParam[], name: string, defaultValue: boolean): boolean;
protected getOptionalBooleanParam(params: ActionParam[], name: string, defaultValue: boolean | undefined): boolean | undefined;
/**
* Gets an optional number parameter with a default value.
* When defaultValue is undefined, returns undefined if the parameter is missing.
*/
protected getOptionalNumberParam(params: ActionParam[], name: string, defaultValue: number): number;
protected getOptionalNumberParam(params: ActionParam[], name: string, defaultValue: number | undefined): number | undefined;
/**
* Gets an optional string array parameter.
*/
protected getOptionalStringArrayParam(params: ActionParam[], name: string): string[] | undefined;
/**
* Shared utility: find a LearnWorlds user by email.
* Returns the user if found, null if not found (empty results).
* Re-throws errors for network failures, auth errors, rate limiting, etc.
*/
FindUserByEmail(email: string, contextUser: UserInfo): Promise<LearnWorldsUser | null>;
/**
* Maps a raw LW API user to the normalized LearnWorldsUser shape.
*/
private mapLWApiUserToLearnWorldsUser;
}
//# sourceMappingURL=learnworlds-base.action.d.ts.map