@churchapps/helpers
Version:
Library of helper functions not specific to any one ChurchApps project or framework.
177 lines (153 loc) • 6.13 kB
text/typescript
import { ApiConfig, RolePermissionInterface, ApiListType } from "./interfaces";
import { ErrorHelper } from "./ErrorHelper";
// Global singleton pattern to ensure single instance across all packages
declare global {
interface Window {
__CHURCHAPPS_API_HELPER__?: ApiHelperClass;
}
var __CHURCHAPPS_API_HELPER__: ApiHelperClass | undefined;
}
class ApiHelperClass {
apiConfigs: ApiConfig[] = [];
isAuthenticated = false;
onRequest: (url:string, requestOptions:any) => void;
onError: (url:string, requestOptions:any, error: any) => void;
getConfig(keyName: string) {
let result: ApiConfig = null;
this.apiConfigs.forEach(config => { if (config.keyName === keyName) result = config });
//if (result === null) throw new Error("Unconfigured API: " + keyName);
return result;
}
setDefaultPermissions(jwt: string) {
this.apiConfigs.forEach(config => {
config.jwt = jwt;
config.permissions = [];
});
this.isAuthenticated = true;
}
setPermissions(keyName: string, jwt: string, permissions: RolePermissionInterface[]) {
this.apiConfigs.forEach(config => {
if (config.keyName === keyName) {
config.jwt = jwt;
config.permissions = permissions;
}
});
this.isAuthenticated = true;
}
clearPermissions() {
this.apiConfigs.forEach(config => { config.jwt = ""; config.permissions = []; });
this.isAuthenticated = false;
}
async get(path: string, apiName: ApiListType) {
const config = this.getConfig(apiName);
if (!config) throw new Error(`API configuration not found: ${apiName}`);
const requestOptions = { method: "GET", headers: { Authorization: "Bearer " + config.jwt } };
return await this.fetchWithErrorHandling(config.url + path, requestOptions);
}
async getAnonymous(path: string, apiName: ApiListType) {
const config = this.getConfig(apiName);
const requestOptions = { method: "GET" };
return await this.fetchWithErrorHandling(config.url + path, requestOptions);
}
async post(path: string, data: any[] | {}, apiName: ApiListType) {
const config = this.getConfig(apiName);
if (!config) throw new Error(`API configuration not found: ${apiName}`);
const requestOptions = {
method: "POST",
headers: { Authorization: "Bearer " + config.jwt, "Content-Type": "application/json" },
body: JSON.stringify(data)
};
return await this.fetchWithErrorHandling(config.url + path, requestOptions);
}
async patch(path: string, data: any[] | {}, apiName: ApiListType) {
const config = this.getConfig(apiName);
if (!config) throw new Error(`API configuration not found: ${apiName}`);
const requestOptions = {
method: "PATCH",
headers: { Authorization: "Bearer " + config.jwt, "Content-Type": "application/json" },
body: JSON.stringify(data)
};
return await this.fetchWithErrorHandling(config.url + path, requestOptions);
}
async delete(path: string, apiName: ApiListType) {
const config = this.getConfig(apiName);
if (!config) throw new Error(`API configuration not found: ${apiName}`);
const requestOptions = {
method: "DELETE",
headers: { Authorization: "Bearer " + config.jwt }
};
if (this.onRequest) this.onRequest(config.url + path, requestOptions);
try {
const response = await fetch(config.url + path, requestOptions);
if (!response.ok) await this.throwApiError(response);
} catch (e) {
console.log(e);
if (this.onError) this.onError(config.url + path, requestOptions, e);
throw (e);
}
}
async postAnonymous(path: string, data: any[] | {}, apiName: ApiListType) {
const config = this.getConfig(apiName);
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
};
return await this.fetchWithErrorHandling(config.url + path, requestOptions);
}
async fetchWithErrorHandling(url: string, requestOptions: any) {
if (this.onRequest) this.onRequest(url, requestOptions);
try {
const response = await fetch(url, requestOptions);
if (!response.ok) await this.throwApiError(response);
else {
if (response.status !== 204 ) {
return response.json();
}
}
} catch (e) {
console.log("Error loading url: " + url);
console.log(e)
throw (e);
}
}
private async throwApiError(response: Response) {
let msg = response.statusText;
try {
msg = await response.text();
} catch {}
try {
const json = await response.json();
msg = json.errors[0];
} catch { }
console.log("RESPONSE", response)
ErrorHelper.logError(response.status.toString(), response.url, msg);
throw new Error(msg || "Error");
}
}
// Force singleton with immediate global assignment
const getGlobalObject = () => {
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof globalThis !== 'undefined') return globalThis;
return {};
};
// Get or create singleton immediately - FORCE SINGLE INSTANCE
const ensureSingleton = () => {
const globalObj = getGlobalObject() as any;
// Use a more unique key to avoid conflicts
const SINGLETON_KEY = '__CHURCHAPPS_API_HELPER_SINGLETON__';
// ALWAYS create a new instance and overwrite any existing one
// This ensures the latest module load wins
if (!globalObj[SINGLETON_KEY]) {
globalObj[SINGLETON_KEY] = new ApiHelperClass();
console.log('🔧 ApiHelper SINGLETON created (new instance)');
} else {
console.log('🔄 ApiHelper SINGLETON already exists - using existing (configs:', globalObj[SINGLETON_KEY].apiConfigs.length, ')');
}
return globalObj[SINGLETON_KEY];
};
// Export the singleton instance
export const ApiHelper = ensureSingleton();
// Also export the class for type usage
export type { ApiHelperClass };