Files
TaroTime/app/app-config.js
T

229 lines
6.9 KiB
JavaScript
Raw Normal View History

2026-03-08 22:24:34 -07:00
(function () {
const apiBaseUrlStorageKey = "tarot-time-api-base-url";
const apiKeyStorageKey = "tarot-time-api-key";
2026-05-29 00:27:03 -07:00
const defaultConnectionAccess = Object.freeze({
connected: false,
apiKeyRequired: false,
authenticated: false,
clientId: "",
accountId: "",
accessLevel: "",
roles: Object.freeze([]),
scopes: Object.freeze([]),
capabilities: Object.freeze({
tarot: false,
adminApiManagement: false
})
});
2026-03-08 22:24:34 -07:00
function normalizeBaseUrl(value) {
return String(value || "")
.trim()
.replace(/\/+$/, "");
}
function normalizeApiKey(value) {
return String(value || "").trim();
}
2026-05-29 00:27:03 -07:00
function normalizeStringList(values) {
return Array.isArray(values)
? values.map((value) => String(value || "").trim()).filter(Boolean)
: [];
}
function normalizeAccessLevel(value) {
return String(value || "").trim().toLowerCase();
}
function normalizeConnectionAccess(source = null) {
const health = source?.health || {};
const auth = health?.auth || source?.auth || {};
const roles = normalizeStringList(auth?.roles);
const scopes = normalizeStringList(auth?.scopes);
const apiKeyRequired = health?.apiKeyRequired === true || source?.apiKeyRequired === true;
const connected = source?.ok === true || source?.connected === true;
const accessLevel = normalizeAccessLevel(
auth?.accessLevel
|| source?.accessLevel
|| (connected && !apiKeyRequired ? "premium" : "")
);
const tarotCapability = source?.capabilities?.tarot === true
|| (connected && !apiKeyRequired)
|| accessLevel === "premium";
const adminApiManagementCapability = source?.capabilities?.adminApiManagement === true
|| roles.includes("admin")
|| scopes.includes("api:admin");
return {
connected,
apiKeyRequired,
authenticated: auth?.authenticated === true,
clientId: String(auth?.clientId || source?.clientId || "").trim(),
accountId: String(auth?.accountId || source?.accountId || "").trim(),
accessLevel,
roles,
scopes,
capabilities: {
tarot: tarotCapability,
adminApiManagement: adminApiManagementCapability
}
};
}
function sameConnectionAccess(left, right) {
return JSON.stringify(left) === JSON.stringify(right);
}
2026-03-08 22:24:34 -07:00
function normalizeConnectionSettings(settings) {
return {
apiBaseUrl: normalizeBaseUrl(settings?.apiBaseUrl),
apiKey: normalizeApiKey(settings?.apiKey)
};
}
function stripLegacyCredentialParams() {
try {
const currentUrl = new URL(window.location.href);
const hadApiKeyParam = currentUrl.searchParams.has("apiKey") || currentUrl.searchParams.has("api_key");
if (!hadApiKeyParam) {
return;
}
currentUrl.searchParams.delete("apiKey");
currentUrl.searchParams.delete("api_key");
const nextUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
window.history.replaceState(window.history.state, "", nextUrl);
} catch (_error) {
}
}
2026-03-08 22:24:34 -07:00
function persistConnectionSettings(settings) {
let didPersist = true;
try {
const params = new URLSearchParams(window.location.search || "");
const queryValue = String(params.get("apiBaseUrl") || "").trim();
if (queryValue) {
window.localStorage.setItem(apiBaseUrlStorageKey, normalizeBaseUrl(queryValue));
}
if (settings.apiBaseUrl) {
window.localStorage.setItem(apiBaseUrlStorageKey, settings.apiBaseUrl);
} else {
window.localStorage.removeItem(apiBaseUrlStorageKey);
}
if (settings.apiKey) {
window.localStorage.setItem(apiKeyStorageKey, settings.apiKey);
} else {
window.localStorage.removeItem(apiKeyStorageKey);
}
} catch (_error) {
didPersist = false;
}
return didPersist;
}
function readConfiguredConnectionSettings() {
try {
const params = new URLSearchParams(window.location.search || "");
const queryValue = String(params.get("apiBaseUrl") || "").trim();
if (queryValue) {
window.localStorage.setItem(apiBaseUrlStorageKey, normalizeBaseUrl(queryValue));
}
const storedBaseUrl = String(window.localStorage.getItem(apiBaseUrlStorageKey) || "").trim();
const storedApiKey = String(window.localStorage.getItem(apiKeyStorageKey) || "").trim();
return normalizeConnectionSettings({
apiBaseUrl: storedBaseUrl,
apiKey: storedApiKey
});
} catch (_error) {
return normalizeConnectionSettings({
apiBaseUrl: "",
apiKey: ""
});
}
}
stripLegacyCredentialParams();
2026-03-08 22:24:34 -07:00
const initialConnectionSettings = readConfiguredConnectionSettings();
2026-05-29 00:27:03 -07:00
const initialConnectionAccess = normalizeConnectionAccess(null);
2026-03-08 22:24:34 -07:00
window.TarotAppConfig = {
...(window.TarotAppConfig || {}),
apiBaseUrl: initialConnectionSettings.apiBaseUrl,
apiKey: initialConnectionSettings.apiKey,
2026-05-29 00:27:03 -07:00
connectionAccess: initialConnectionAccess,
2026-03-08 22:24:34 -07:00
getApiBaseUrl() {
return normalizeBaseUrl(this.apiBaseUrl);
},
getApiKey() {
return normalizeApiKey(this.apiKey);
},
isConnectionConfigured() {
return Boolean(this.getApiBaseUrl());
},
getConnectionSettings() {
return {
apiBaseUrl: this.getApiBaseUrl(),
apiKey: this.getApiKey()
};
},
2026-05-29 00:27:03 -07:00
getConnectionAccess() {
return normalizeConnectionAccess(this.connectionAccess || defaultConnectionAccess);
},
hasTarotAccess() {
return this.getConnectionAccess().capabilities.tarot === true;
},
hasAdminApiManagementAccess() {
return this.getConnectionAccess().capabilities.adminApiManagement === true;
},
updateConnectionAccess(nextAccess = null) {
const previous = this.getConnectionAccess();
const current = normalizeConnectionAccess(nextAccess);
this.connectionAccess = current;
if (!sameConnectionAccess(previous, current)) {
document.dispatchEvent(new CustomEvent("connection:access-updated", {
detail: {
previous,
current: { ...current, roles: [...current.roles], scopes: [...current.scopes], capabilities: { ...current.capabilities } }
}
}));
}
return current;
},
2026-03-08 22:24:34 -07:00
updateConnectionSettings(nextSettings = {}) {
const previous = this.getConnectionSettings();
const current = normalizeConnectionSettings({
...previous,
...nextSettings
});
const didPersist = persistConnectionSettings(current);
this.apiBaseUrl = current.apiBaseUrl;
this.apiKey = current.apiKey;
if (previous.apiBaseUrl !== current.apiBaseUrl || previous.apiKey !== current.apiKey) {
document.dispatchEvent(new CustomEvent("connection:updated", {
detail: {
previous,
current: { ...current }
}
}));
}
return {
...current,
didPersist
};
}
};
})();