@gphone/sdk
    Preparing search index...

    Function useAppRegistry

    • OS Service Hook for dynamic app registry & remote app installation.

      registerApp(manifest, component) is the in-process path — core apps, and (dev-only) runtime fixtures. registerAddOn(manifest, source?) is everything else: an add-on's bundle is source text, run in a sandboxed iframe, never a component the shell executes itself (GPHONE-16 step 4). A caller inside a sandboxed add-on gets a twin where every mutating member — including registerAddOn — throws: only a core app installs or removes apps.

      Returns {
          bundledAddOns: AppManifest[];
          getFirstBootTime: () => string;
          installFromCatalog: (
              entry: CatalogEntry,
          ) => Promise<{ manifest: AppManifest }>;
          registerAddOn: (manifest: AppManifest, source?: string) => void;
          registerApp: (manifest: AppManifest, component: AppComponent) => void;
          registryStore: {
              getAddOnSource: (appId: string) => Promise<string | undefined>;
              getComponent: (appId: string) => AppComponent | undefined;
              getManifest: (appId: string) => AppManifest | undefined;
              installFromCatalog: (
                  entry: CatalogEntry,
              ) => Promise<{ manifest: AppManifest }>;
              isInstalled: (appId: string) => boolean;
              isKnownApp: (appId: string) => boolean;
              loadComponent: (appId: string) => Promise<AppComponent | undefined>;
              registerAddOn: (manifest: AppManifest, source?: string) => void;
              registerApp: (manifest: AppManifest, component: AppComponent) => void;
              rehydrateSavedRemoteApps: () => Promise<void>;
              subscribe: (
                  this: void,
                  run: Subscriber<AppManifest[]>,
                  invalidate?: () => void,
              ) => Unsubscriber;
              unregisterApp: (appId: string) => void;
          };
          unregisterApp: (appId: string) => void;
      }

      • bundledAddOns: AppManifest[]

        Add-ons this repo ships uninstalled — what the Store has to offer beyond remotes.

      • getFirstBootTime: () => string
      • installFromCatalog: (entry: CatalogEntry) => Promise<{ manifest: AppManifest }>
      • registerAddOn: (manifest: AppManifest, source?: string) => void
      • registerApp: (manifest: AppManifest, component: AppComponent) => void
      • registryStore: {
            getAddOnSource: (appId: string) => Promise<string | undefined>;
            getComponent: (appId: string) => AppComponent | undefined;
            getManifest: (appId: string) => AppManifest | undefined;
            installFromCatalog: (
                entry: CatalogEntry,
            ) => Promise<{ manifest: AppManifest }>;
            isInstalled: (appId: string) => boolean;
            isKnownApp: (appId: string) => boolean;
            loadComponent: (appId: string) => Promise<AppComponent | undefined>;
            registerAddOn: (manifest: AppManifest, source?: string) => void;
            registerApp: (manifest: AppManifest, component: AppComponent) => void;
            rehydrateSavedRemoteApps: () => Promise<void>;
            subscribe: (
                this: void,
                run: Subscriber<AppManifest[]>,
                invalidate?: () => void,
            ) => Unsubscriber;
            unregisterApp: (appId: string) => void;
        }
        • getAddOnSource: (appId: string) => Promise<string | undefined>

          Fetch an add-on's bundle text, never its component — the sandboxed iframe transport runs it, this registry only ever hands over bytes. Idempotent, same as loadComponent.

        • getComponent: (appId: string) => AppComponent | undefined
        • getManifest: (appId: string) => AppManifest | undefined

          The manifest for an app the shell can render — installed, or a bundled add-on.

          The shell holds app ids; anything shown to a player needs the manifest's name. Without this the error boundary rendered the id rather than the manifest's name, which are not the same string.

          The addOns fallback exists because installed and renderable are not the same set for a core: false app. isKnownApp already counts a bundled add-on as known whether or not it has been installed, so openApp('notes') from a ?app= deep link legitimately makes it the current app — but Shell.svelte renders through {#if manifest && ...}, so an installed-only lookup left that path on a permanent spinner. Opening an uninstalled add-on straight from a deep link is by design (it is how the dev harness reaches one), so the manifest has to resolve for it. It is still a fact about the build, not the install: addOns is written once at startup from the manifest glob and nothing that is not in this bundle can appear in it.

        • installFromCatalog: (entry: CatalogEntry) => Promise<{ manifest: AppManifest }>

          The only way a remote app is ever installed. Builds the manifest from entry — never from anything the fetched bundle itself claims to be — after the bundle's bytes are hash-verified against entry.sha256. See installVerified below.

        • isInstalled: (appId: string) => boolean

          Whether the app has actually been installed — the question getManifest used to be asked in place of, and stopped being able to answer.

          getManifest resolves a bundled add-on that has never been installed (see its own comment), which is right for rendering one opened by a deep link and wrong for any gate that means "the player has this app". nuiMessages' appEvent is exactly such a gate: it reads the manifest's permissions to decide whether a pushed toast is allowed, and with the fallback in place a never-installed Blabber would have started raising toasts. Installed-ness is its own fact, so it gets its own question.

        • isKnownApp: (appId: string) => boolean

          Whether the app exists, regardless of whether its chunk has arrived.

        • loadComponent: (appId: string) => Promise<AppComponent | undefined>

          Fetch an app's component. Idempotent, and the only thing that imports app code.

        • registerAddOn: (manifest: AppManifest, source?: string) => void

          Register a manifest with its bundle as source text, never executed here.

          source given explicitly — a catalog install (installVerified, already hash-verified) or a dev-registered add-on — is stashed as-is. Omitted, manifest.id must be one of this build's own bundled add-ons (addOnIds); its text is left for getAddOnSource to fetch lazily, on first open, rather than eagerly here.

        • registerApp: (manifest: AppManifest, component: AppComponent) => void

          Register a manifest with its component already loaded — the in-process path.

          Stays for core apps, which always ran in-process. For a core: false manifest this is dev-only: error_boundary.spec.ts registers runtime crash fixtures through it, and nothing shipped is meant to reach it — a real add-on registers through registerAddOn below, as source text, and runs sandboxed. Blocked outside import.meta.env.DEV so a production build can't be handed a live component for an app that never went through the sandboxed transport.

        • rehydrateSavedRemoteApps: () => Promise<void>
        • subscribe: (
              this: void,
              run: Subscriber<AppManifest[]>,
              invalidate?: () => void,
          ) => Unsubscriber
        • unregisterApp: (appId: string) => void
      • unregisterApp: (appId: string) => void