import { clone, delay, extractErrorMsg, getTimestampInTicks } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs"; import { IbGib_V1, Rel8n } from "@ibgib/ts-gib/dist/V1/types.mjs"; import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs"; import { GIB } from "@ibgib/ts-gib/dist/V1/constants.mjs"; import { Gib, IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs"; import { getGibInfo, isPrimitive } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs"; import { mut8 } from "@ibgib/ts-gib/dist/V1/transforms/mut8.mjs"; import { getSpecialConfigKey, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs"; import { execInSpaceWithLocking, persistTransformResult } from "@ibgib/core-gib/dist/witness/space/space-helper.mjs"; import { MetaspaceService } from "@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs"; import { IbGibSpaceAny } from "@ibgib/core-gib/dist/witness/space/space-base-v1.mjs"; import { SpecialIbGibType } from "@ibgib/core-gib/dist/common/other/other-types.mjs"; import { DEFAULT_IBGIB_COLOR, DEFAULT_IBGIB_COLOR_CONTRAST, DEFAULT_IBGIB_TRANSLUCENT, GLOBAL_LOG_A_LOT, GLOBAL_THIS_IBGIB_KEY, } from "./constants.mjs"; import { GibColorInfo, IbGibGlobalThisInfo } from "./types.mjs"; import { getContextModeService } from "./context/context-mode-service.mjs"; const logalot = GLOBAL_LOG_A_LOT; // change this when you want to turn off verbose logging export async function getTagsIbGib({ metaspace, space, }: { metaspace: MetaspaceService, space: IbGibSpaceAny | undefined, }): Promise { const lc = `[${getTagsIbGib.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 2ccb79537a75e7896bace84e6c710d25)`); } const tagsIbGib = await metaspace.getSpecialIbGib({ type: SpecialIbGibType.tags, space }); if (!tagsIbGib) { throw new Error(`tagsIbGib not found in metaspace. space: ${!!space ? space.ib : 'default local user space'} (E: 4734c510a411274315c0add8f70e1925)`); } return tagsIbGib; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * NOTE: This was originally written only for agents and I'm generalizing it to * any "coupled" ibgib. The idea is that for local-space-specific ibgibs that * are related to domain ibgibs, but where we don't want to modify the domain * ibgib directly, we create this "coupled" ibgib. We then index it in a local * space special (index) ibgib and associate it by tjpAddr. * * Generates a consistent special name for the agent index ibGib based on the * domain ibGib's primitive ancestor's ib or the ibGibAddr's ib atom. * * RIGHT NOW I"M PRIORITIZING THE ATOM. I THINK IT ALWAYS RETURNS THE ATOM, I.E., THE FIRST SPACE-DELIMITED PIECE OF THE `ib`. * * I am doing this because the project ibgib actually forks from a comment * ibgib, so its primitive ancestor is "comment^gib". * * ## notes * * We are trying to get a special ibgib index that maps from ibgibs (the domain) * to what local agent is registered to handle that ibgib's timeline. * * Concretely, we're working on the project ibgibs and we need a way to get at * the agent that is assigned to that project. But we can't just mut8 the * project itself because this would cause issues once the project is spread out * among multiple execution contexts (similar to "remotes" in git). * * So we will have a special ibgib registered with the local space which will * act as this index that maps ibgib -> handling agent. This function helps get * the *name* of that special indexing ibgib. * * This can be driven either by the ibgib itself, via the last primitive ancestor * ibgib's address, or via the ib's atom (the first space-delimited term in the * `ib` metadata). * * So if an ancestor is [comment^gib, specialComment^gib, specialComment * abc123^ABC123.DEF456] (where the abc123 things are representative of the full * hashes 64 characters long atow since we're using sha256), then the *last* * primitive address is specialComment^gib. */ export function getIndexNameFromIbGib({ scope, ibGib, ibGibAddr, defaultNameIfError, }: { /** * what kind of index are we talking in general? */ scope: string, ibGib?: IbGib_V1, ibGibAddr?: IbGibAddr, /** * if truthy and an error is produced, then this will be returned. */ defaultNameIfError?: string, }): string { const lc = `[${getIndexNameFromIbGib.name}]`; try { if (!ibGib && !ibGibAddr) { throw new Error(`${lc} Must provide either ibGib or ibGibAddr. (E: 6627e87b36f8c13748ba228800301726)`); } if (scope.includes(' ')) { console.warn(`${lc} scope includes one or more spaces. These will be converted to double underscores. (W: 1554a81454e8ae0a6858ea5dd17f8925)`); scope = scope.replace(/ /g, '__'); } /** * note: non-alphanumerics are replaced with underscores */ let primitiveAncestorIb: IbGibAddr | undefined = undefined; let atom: string | undefined = undefined; /** * This is what we're trying to extract. once we have this, then we can * simply append our index name, e.g. `${ancestorIbOrAtom}-agent-index` */ let ancestorIbOrAtom: string | undefined = undefined; if (ibGib) { // go by the ibgib's full data if (!ibGib.data) { throw new Error(`(UNEXPECTED) ibGib.data falsy? (E: 1dae78107356afa1385fde481f1adf26)`); } if (!ibGib.rel8ns) { throw new Error(`(UNEXPECTED) ibGib.rel8ns falsy? (E: 4019f8c7a3d8e32cc8a4dab8b5556826)`); } if (!ibGib.rel8ns.ancestor) { throw new Error(`(UNEXPECTED) ibGib.rel8ns.ancestor falsy? (E: c02ec8806b9879d75ea2c9759b263826)`); } if (ibGib.rel8ns.ancestor.length === 0) { throw new Error(`(UNEXPECTED) ibGib.rel8ns.ancestor.length === 0? there should be at least one ancestor (E: 6fa0a890a57412e9282efcc8f91da526)`); // this may turn into a warning later on down the road and return based on the ibGib's addr } if (ibGib && ibGibAddr) { if (getIbGibAddr({ ibGib }) !== ibGibAddr) { throw new Error(`(UNEXPECTED) both ibGib and ibGibAddr provided, but addr of ibGib doesn't equal ibGibAddr? (E: 3db28445092a68cdc2ac58e857a8e826)`); } } // const primitiveAncestor = getPrimitiveAncestor({ ibGib }); const primitiveAncestorAddrs = ibGib.rel8ns.ancestor!.filter(x => isPrimitive({ gib: getIbAndGib({ ibGibAddr: x }).gib })); if (primitiveAncestorAddrs.length === 0) { debugger; // error no primitive ancestor addrs? console.error(`${lc} ibGib has no primitive ancestor addrs? Still going to work off of the ib then, which should have the atom as the first space-delimited term. (E: f043d1353b584f3cb8c822eeb0e8f826)`) } const primitiveAncestorAddr = primitiveAncestorAddrs.at(-1)!; primitiveAncestorIb = getIbAndGib({ ibGibAddr: primitiveAncestorAddr }).ib; // replace ALL spaces with underscores ancestorIbOrAtom = primitiveAncestorIb; } // get the atom from the address ibGibAddr ??= getIbGibAddr({ ibGib }); // try to get at it from the ibGibAddr const { ib } = getIbAndGib({ ibGibAddr }); atom = (ib.split(' ').at(0) ?? ''); // at this point we have a raw atom and possibly a raw primitive // ancestor ib. we want to compare the two, which usually should be the // same (i think?). if different, warn and return atom = atom.replace(/\W/g, '_'); primitiveAncestorIb ??= atom; primitiveAncestorIb = primitiveAncestorIb.replace(/\W/g, '_'); if (!primitiveAncestorIb && !atom) { throw new Error(`(UNEXPECTED) both primitiveAncestorIb and atom falsy? one should be truthy at this point. (E: 9128b8f4a32831a7a807232b7ae14426)`); } // if (primitiveAncestorIb !== atom) { // // warn and do per arg // console.warn(`${lc} ibGib primitive ancestor ib (${primitiveAncestorIb}) doesn't match atom (${atom}). going with ancestorIbOrAtom: ${ancestorIbOrAtom} (W: 0d3e19765e18c8993899e5f608dcb526)`); // } ancestorIbOrAtom = atom.toLowerCase(); if (logalot) { console.log(`${lc} ancestorIbOrAtom: ${ancestorIbOrAtom} (I: 1ca518f4b3b3dc7fa8372f5f1fd72f26)`); } /** * I forgot, this cannot be kebab-cased because we only allow \w regexp * on the special ibgib name. I don't remember the reasoning for this, * may be arbitrary. But for now, going to change this to underscore, * even though the atom or ancestor ib may have underscores in it. Ah * well. */ const indexName = `${ancestorIbOrAtom}_${scope}index`; if (logalot) { console.log(`${lc} indexName: ${indexName} (I: 1b7868b711684efa53a4def8cddf6826)`); } return indexName; } catch (error) { if (!!defaultNameIfError) { console.warn(`${lc} error happened but defaultNameIfError (${defaultNameIfError}) provided. So will return this value. error that was thrown: ${extractErrorMsg(error)}`); return defaultNameIfError; } else { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } } } /** * NOTE: This was originally written only for agents and I'm generalizing it to * any "coupled" ibgib. The idea is that for local-space-specific ibgibs that * are related to domain ibgibs, but where we don't want to modify the domain * ibgib directly, we create this "coupled" ibgib. We then index it in a local * space special (index) ibgib and associate it by tjpAddr. * Retrieves the agent ibGib associated with a domain ibGib from the special agent index ibGib. * * may have to change this to require the ibgib, not give an optional * domainIbGibAddr */ export async function getLocalCoupledIbGibForDomainIbGib({ scope, ibGib, metaspace, space, skipGetLatest, }: { scope: string, ibGib: IbGib_V1, metaspace: MetaspaceService, space: IbGibSpaceAny, /** * if true, will not call getLatest on the coupled ibgib if found. */ skipGetLatest?: boolean, }): Promise { const lc = `[${getLocalCoupledIbGibForDomainIbGib.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: f19dc86aab380fe809aef27ccc7a7425)`); } if (!scope) { throw new Error(`scope required to be a non-empty string (E: 73e702495c38c7d8747d1cf8850af825)`); } if (!ibGib) { throw new Error(`ibGib required (E: 9f71a8c016daf9707ed9b098262ca826)`); } // Get the index name and domain TJP address const indexSpecialIbGibType: SpecialIbGibType = getIndexNameFromIbGib({ scope, ibGib, }) as SpecialIbGibType; const domainTjpAddr = getTjpAddr({ ibGib, defaultIfNone: 'incomingAddr' })!; // Get the index ibGib const fn: () => Promise = async () => { const specialIndex = await metaspace.getSpecialIbGib({ type: indexSpecialIbGibType, space, initialize: false, // Do not create if it doesn't exist when retrieving dontWarnIfNotExist: true, // Don't warn if the index doesn't exist lock: true, }); // If the index ibGib doesn't exist, nothing is registered for this domain ibgib if (!specialIndex || !specialIndex.data) { if (logalot) { console.log(`${lc} special ${scope} index ibGib not found for indexSpecialIbGibType (${indexSpecialIbGibType}). No coupled ibgib registered, returning undefined. (I: eb5f53cb78a22be4d819b3bd15e37b26)`); } return undefined; /* <<<< returns early */ } // Get the address from the map const coupleMap: { [tjpAddr: string]: IbGibAddr } = specialIndex.data.coupleMap ?? specialIndex.data.agentMap ?? {}; const coupledAddr = coupleMap[domainTjpAddr]; if (!coupledAddr) { if (logalot) { console.log(`${lc} No coupled ${scope} ibgib address found for domain ${domainTjpAddr} in the coupled index map. indexSpecialIbGibType: ${indexSpecialIbGibType}. returning undefined. (I: 3f0966a72076241947446bb81dcca726)`); } return undefined; /* <<<< returns early */ } let addrToGet = skipGetLatest ? coupledAddr : await metaspace.getLatestAddr({ addr: coupledAddr, space }) ?? coupledAddr; let resGet = await metaspace.get({ addrs: [addrToGet], space, }); if (resGet.errorMsg || (resGet.ibGibs ?? []).length !== 1) { throw new Error(`couldn't get latest coupled ibgib (${addrToGet}) for domain ${domainTjpAddr}. (E: 8aa7b25f5cd4ebadd8bf2f8807dfe825)`); } return resGet.ibGibs![0] as TIbGib; } const result = await execInSpaceWithLocking({ scope: indexSpecialIbGibType, secondsValid: 180, // Example timeout maxDelayMs: 100, // Example delay maxLockAttempts: 1800, // Example attempts space, callerInstanceId: lc + getTimestampInTicks(), fn, }); return result; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * Registers a domain ibGib with its associated agent ibGib in the special agent * index ibGib. This creates or updates a mapping from the domain ibGib's TJP * address to the agent's address. * * ## intent * * we want to rel8 ibgibs but not directly change the domain ibgib. Like if we * have an ibgib that we want to collaborate on, we want a mechanism for the * local space (later local user via keystone) to have settings, agents, etc., * for that domain ibgib. But each space has its own version, like a local/user * settings vs. a workspace settings. * * So this gets/creates an index on the local space and associates the domain * with the local. * * @example * use this to couple a local agent to a domain ibgib or a local settings ibgib * to a domain ibgib, that you do not want to share with others who will have * their own versions. */ export async function coupleDomainIbGibWithLocalIbGibViaIndex({ scope, domainIbGib, localIbGib, metaspace, space, }: { scope: string, domainIbGib: IbGib_V1, localIbGib: IbGib_V1, metaspace: MetaspaceService, space: IbGibSpaceAny, }): Promise { const lc = `[${coupleDomainIbGibWithLocalIbGibViaIndex.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 217ae68e0c820e48f4c00409f7f14325)`); } const indexSpecialIbGibType: SpecialIbGibType = getIndexNameFromIbGib({ scope, ibGib: domainIbGib, }); const domainTjpAddr = getTjpAddr({ ibGib: domainIbGib }); const localAddr = getIbGibAddr({ ibGib: localIbGib }); if (!domainTjpAddr) { throw new Error(`${lc} Could not get TJP address for domain ibGib. (E: 35a4e8ac291b0b85cb958e28dd812f25)`); } if (!localAddr) { throw new Error(`${lc} Could not get address for agent ibGib. (E: 2fce1187630b45934c4155a23204d125)`); } const fn = async () => { // Get or create the agent index ibGib const agentSpecialIndex = await metaspace.getSpecialIbGib({ type: indexSpecialIbGibType, space, initialize: true, // Create if it doesn't exist dontWarnIfNotExist: true, lock: false, }); if (!agentSpecialIndex) { throw new Error(`${lc} Could not get or create agent index ibGib.`); } // Initialize data if it doesn't exist if (!agentSpecialIndex.data) { throw new Error(`(UNEXPECTED) agentSpecialIndex.data falsy? (E: 74299eed147debf6398ca37e7b8d5c25)`); } // Get the current mapping or initialize an empty one const map = agentSpecialIndex.data.coupleMap ?? agentSpecialIndex.data.agentMap; const coupleMap: { [tjpAddr: string]: IbGibAddr } = !!map ? clone(map) : {}; // Update the mapping const existingMappedAgentAddr = coupleMap[domainTjpAddr]; if (existingMappedAgentAddr) { if (existingMappedAgentAddr === localAddr) { if (logalot) { console.log(`${lc} domain ibGib (${domainTjpAddr}) already mapped to agent (${localAddr}). returning early (I: cb5d4dcef8cf14de7336120fb8ffef25)`); } return; /* <<<< returns early */ } else { console.warn(`${lc} domainTjpAddr (${domainTjpAddr}) already assigned to existingMappedAgentAddr (${existingMappedAgentAddr}). We will be changing to new localAddr (${localAddr}). (W: eb993e33f7731e570fa76f24a2d80625)`) } } coupleMap[domainTjpAddr] = localAddr; // Assign the updated map back to data Save the updated index ibGib. // there is extra plumbing involved with special index ibgibs. see // `rel8ToSpecialIbGib` for an example in // libs/core-gib/src/witness/space/space-helper.mts // await updateSpecialIndex({ // metaspace, // type: indexSpecialIbGibType, // dataToAddOrPatch: { coupleMap }, // space, // }); const resNewSpecial = await mut8({ src: agentSpecialIndex, dataToAddOrPatch: { coupleMap }, dna: false, linkedRel8ns: [Rel8n.past], nCounter: true, }); const newSpecialIbGib = resNewSpecial.newIbGib; // persist await persistTransformResult({ resTransform: resNewSpecial, space }); // update the space ibgib which contains the special/config information const configKey = getSpecialConfigKey({ type: indexSpecialIbGibType }); await metaspace.setConfigAddr({ key: configKey, addr: getIbGibAddr({ ibGib: newSpecialIbGib }), space, }); await metaspace.registerNewIbGib({ ibGib: newSpecialIbGib, space, }); } await execInSpaceWithLocking({ scope: indexSpecialIbGibType, // Use the special index type as the lock scope. secondsValid: 60, // Example timeout maxDelayMs: 100, // Example delay maxLockAttempts: 600, // Example attempts space, // callerInstanceId: // You might need a way to generate a unique ID for the caller fn, }); if (logalot) { console.log(`${lc} Registered domain ${domainTjpAddr} with agent ${localAddr}`); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * Returns a function, that, as long as it continues to be invoked, will not * be triggered. The function will be called after it stops being called for * N milliseconds. If `immediate` is passed, trigger the function on the * leading edge, instead of the trailing. * * @param func The function to debounce. * @param wait The number of milliseconds to wait after the last call. * @param immediate If true, trigger the function on the leading edge. * @returns A debounced version of the function. */ export function debounce void>( func: T, wait: number, immediate?: boolean ): (...args: Parameters) => void { let timeout: ReturnType | null = null; return function (this: any, ...args: Parameters): void { const context = this; const later = function (): void { timeout = null; if (!immediate) { func.apply(context, args); } }; const callNow = immediate && !timeout; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(later, wait); if (callNow) { func.apply(context, args); } }; } export function getMaskedSecret({ secret, countToShow }: { secret: string, countToShow: number }): string { const lc = `[${getMaskedSecret.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 4d479c342b77f12568501c085b4b0425)`); } if (secret) { if (countToShow > secret.length) { if (secret.length === 1) { console.warn(`${lc} secret is one character? (W: 64f2cf0ea838e2846239175951e98826)`); return secret; // whatever } else { console.warn(`${lc} countToShow is longer than the entire secret! just showing the last two (W: a428a8a27aedc610a813f6b38e5bae26)`); return `****${secret.substring(secret.length - 2)}`; } } else { // show the last countToShow const secretMasked = `****${secret.substring(secret.length - countToShow)}`; return secretMasked; } } else { console.error(`${lc} secret falsy? empty? returning empty string. (E: ad673876c3f8a74d9875682bcde96826)`); return ''; } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } export function insertAt({ newItems, index, targetArray, }: { newItems: T[], index: number, targetArray: T[], }): T[] { const lc = `[${insertAt.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 5fc3c84d9e08f4ee88978be8bd117a25)`); } if (index < 0 || index >= targetArray.length) { throw new Error(`index (${index}) is out of bounds for targetArray (length ${targetArray.length}) (E: ae5f08e8db94a6f3e89c39b896538826)`); } const arrayCopy = [...targetArray]; arrayCopy.splice(index, 0, ...newItems); return arrayCopy; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } export function deleteAt({ targetArray, index, }: { targetArray: T[], index: number, }): T[] { const lc = `[${deleteAt.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 6f2df80039f897a0883714686af97925)`); } if (index < 0 || index >= targetArray.length) { throw new Error(`index (${index}) is out of bounds for targetArray (length ${targetArray.length}) (E: eb0d44828e88f8bc280c7b48885fa826)`); } const arrayCopy = [...targetArray]; arrayCopy.splice(index, 1); return arrayCopy; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } export function getShortenedStringWithEllipsis({ str, maxChars, }: { str: string, maxChars: number, }): string { const lc = `[${getShortenedStringWithEllipsis.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 228729c84d2805bc186e96c8cbc56825)`); } if (str.length <= maxChars) { return str; } else { return str.substring(0, maxChars - 1) + '…'; } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * location for globals to be used across ALL ibgib apps/libs, present and future. * * NOTE: Very little should be in here, since it pertains to such a broad scope. * * ## how to use this class * * ibgib apps are expected to have a metaspace singleton, and this is expected to be */ export class IbGibGlobalThis_IbGib { private lc: string = `[${IbGibGlobalThis_IbGib.name}]`; private _metaspacesByAppId: { [appId: string]: MetaspaceService } = {}; private _globalThisObjectsByAppId: { [appId: string]: any } = {}; constructor() { } /** * The metaspace should already be initialized. * * @throws if metaspace.initialized is falsy * * @see {@link IbGibGlobalThis_IbGib} for general instructions on how to use * this class. */ public async registerInitializedMetaspace({ appId, metaspace, }: { appId: string, metaspace: MetaspaceService, }): Promise { const lc = `${this.lc}[${this.registerInitializedMetaspace.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: d65058fb49953ac8bff6823cbc464825)`); } if (this._metaspacesByAppId[appId]) { throw new Error(`appId (${appId}) already has a metaspace registered. (E: 1601f88722b849bdd85b6b98075e8225)`); } if (metaspace.initialized) { this._metaspacesByAppId[appId] = metaspace; } else { throw new Error(`metaspace.initialized is false. (E: 00e361cff998dd54e4f7b8a10106ed25)`); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * sync call to a metaspace that was registered by the appId. * * @throws If no metaspace was registered with the {@link appId} * * @see {@link IbGibGlobalThis_IbGib} for general instructions on how to use * this class. */ getMetaspace({ appId }: { appId: string }): MetaspaceService { const lc = `${this.lc}[${this.getMetaspace.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: b918969698bf80cf18a03a3b91385525)`); } const metaspace = this._metaspacesByAppId[appId]; if (metaspace) { return metaspace; } else { throw new Error(`NOT FOUND: appId (${appId}) does not have a metaspace registered. (E: 0ebcf95fb208d2d3f27edb06260d9d25)`); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * Registers an app-specific global object for global state with ibgib apps, * on the ibgib global object (atow 12/2025 this is `globalThis.ibgib`). * @see {@link IbGibGlobalThis_IbGib.getGlobalThis} * * @see {@link IbGibGlobalThis_IbGib} for general instructions on how to use * this class. */ registerGlobalThis({ appId, appGlobal }: { appId: string, appGlobal: TGlobalInfo }): void { const lc = `${this.lc}[${this.registerGlobalThis.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 0680ec122948fe23cd0f4c3a61451825)`); } if (this._globalThisObjectsByAppId[appId]) { throw new Error(`appId (${appId}) already has a globalThis object registered. (E: 1e71e8859a8b94c9a85af595818cd825)`); } this._globalThisObjectsByAppId[appId] = appGlobal; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * Get the app-specific global object on globalThis.ibgib that was * (or should have been) registered by the {@link appId} * * @throws if no globalThis obj was registered with `appId` * @see {@link IbGibGlobalThis_IbGib.registerGlobalThis} * * @see {@link IbGibGlobalThis_IbGib} for general instructions on how to use * this class. */ getGlobalThis({ appId }: { appId: string }): TGlobalInfo { const lc = `${this.lc}[${this.getGlobalThis.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: f59f58a8344664092877a0f8b43c1125)`); } const globalThisObj = this._globalThisObjectsByAppId[appId] as (TGlobalInfo | undefined); if (globalThisObj) { return globalThisObj; } else { throw new Error(`NOT FOUND: appId (${appId}) does not have a globalThis object registered. (E: 8e3b08d05c475399781871d2a12ba825)`); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } } /** * */ export function getIbGibGlobalThis_IbGib(): IbGibGlobalThisInfo { const lc = `[${getIbGibGlobalThis_IbGib.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 890f53e984d69461d9f4ded8741fcc25)`); } let result = globalThis[GLOBAL_THIS_IBGIB_KEY]; if (!result) { console.log(`${lc} initializing globalThis.${GLOBAL_THIS_IBGIB_KEY}... (I: 9d9ff1813448d9a1b82b0e5790a74825)`); result = {}; globalThis[GLOBAL_THIS_IBGIB_KEY] = result; } return result; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * helper that waits until the global metaspace is initialized and then returns * that metaspace. * * NOTE: This does not have a max count breakout or cancellation. The metaspace * is simply integral to the website and this is not expected to fail (otherwise * the entire website is foo anyway). * * ## driving use case * * when the router tries to load a route that is based on an ibgib, the * metaspace is required. */ export async function getGlobalMetaspace_waitIfNeeded({ delayIntervalMs = 50 }: { delayIntervalMs: number } = { delayIntervalMs: 50 }): Promise { const lc = `[${getGlobalMetaspace_waitIfNeeded.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 4acc22d08dd1100503f9b0423f302925)`); } const contextSvc = getContextModeService(); if (contextSvc.hasActiveContext) { return contextSvc.current.metaspace; } return await contextSvc.getPersistentMetaspace_waitIfNeeded({ delayIntervalMs }); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * builds a color schema based on the gib. * * note that the alpha drives the translucent property, but the solid color is * always built */ export function getDeterministicColorInfo({ ibGibAddr, gib, ibGib, translucentAlpha = 10, }: { ibGibAddr?: IbGibAddr, gib?: Gib, ibGib?: IbGib_V1, /** * drives the translucent color. * * @default 10 */ translucentAlpha?: number }): GibColorInfo { const lc = `[${getDeterministicColorInfo.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 3b70af42d1c4f6ae91870f3a15c6c625)`); } if (!ibGibAddr && !gib && !ibGib) { gib = GIB; } gib ??= ibGib?.gib ?? getIbAndGib({ ibGibAddr }).gib; const gibInfo = getGibInfo({ gib }); translucentAlpha ??= 10; /** * we want to color coordinate according to timeline if possible, i.e., * by tjpGib. */ let drivingHash = ''; if (gibInfo.isPrimitive) { return { gib, gibInfo, punctiliarColor: DEFAULT_IBGIB_COLOR, punctiliarColorTranslucent: DEFAULT_IBGIB_TRANSLUCENT, punctiliarColorContrast: DEFAULT_IBGIB_COLOR_CONTRAST, }; /* <<<< returns early */ } else { drivingHash = gibInfo.tjpGib ?? gibInfo.punctiliarHash ?? gib; // hmm, gib should be punctiliar but meh } /** * gets the color string */ // const fnGetColorStr = (drivingHash: string) => { // function getContrastColor(hexcolor: string) { // const c = hexcolor.concat().replace("#", ""); // var r = parseInt(c.substr(0, 2), 16); // var g = parseInt(c.substr(2, 2), 16); // var b = parseInt(c.substr(4, 2), 16); // var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; // return (yiq >= 128) ? '#000000' : '#FFFFFF'; // } // let alphaStr = ''; // if (translucentAlpha || translucentAlpha === 0) { // if (translucentAlpha < 0 || translucentAlpha > 100) { // throw new Error(`alpha must be between 0 and 100 (E: c78faeb1477482c739b03dd32d2e1825)`); // } // if (translucentAlpha === 100) { // alphaStr = ''; // } else if (translucentAlpha >= 0 && translucentAlpha < 10) { // // single digit // alphaStr = `0${translucentAlpha}`; // } else { // alphaStr = translucentAlpha.toString(); // } // } // const color = `#${drivingHash.substring(0, 6)}`; // const colorTranslucent = `#${drivingHash.substring(0, 6)}${alphaStr}`; // const colorContrast = getContrastColor(color); // return [color, colorTranslucent, colorContrast]; // }; const [punctiliarColor, punctiliarColorTranslucent, punctiliarColorContrast] = getColorStrings(translucentAlpha, gibInfo.punctiliarHash ?? gib); const [tjpColor, tjpColorTranslucent, tjpColorContrast] = !!gibInfo.tjpGib ? getColorStrings(translucentAlpha, gibInfo.tjpGib) : [undefined, undefined, undefined]; return { gib, gibInfo, punctiliarColor, punctiliarColorTranslucent, punctiliarColorContrast, tjpColor, tjpColorTranslucent, tjpColorContrast, }; } catch (error) { const errorMsg = `${lc} ${extractErrorMsg(error)}`; console.error(errorMsg); return { gib: gib ?? GIB, gibInfo: { isPrimitive: true }, punctiliarColor: '#ff0000', // red punctiliarColorTranslucent: '#ff000010', // translucent red punctiliarColorContrast: '#ffffff', // black errorMsg, }; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * Converts HSL color values to a standard 6-character hex string. */ function hslToHex(h: number, s: number, l: number): string { l /= 100; const a = (s * Math.min(l, 1 - l)) / 100; const f = (n: number) => { const k = (n + h / 30) % 12; const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * color).toString(16).padStart(2, '0'); }; return `#${f(0)}${f(8)}${f(4)}`; } /** * gets the color, colorTranslucent and colorContrast */ export function getColorStrings(translucentAlpha: number, drivingHash: string): [string, string, string] { const hashVal = parseInt(drivingHash.substring(0, 8), 16) || 0; const hue = hashVal % 360; // Locked Saturation (65) and Lightness (62) for beautiful pastel UI tones const color = hslToHex(hue, 65, 62); const r = parseInt(color.substring(1, 3), 16); const g = parseInt(color.substring(3, 5), 16); const b = parseInt(color.substring(5, 7), 16); const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; // Premium soft dark text vs off-white text const colorContrast = (yiq >= 128) ? '#11111b' : '#ffffff'; const alphaScale = Math.min(100, Math.max(0, translucentAlpha)); const alphaHex = Math.round((alphaScale / 100) * 255) .toString(16) .padStart(2, '0'); const colorTranslucent = `${color}${alphaHex}`; return [color, colorTranslucent, colorContrast]; };