Files
template-app/apps/web/src/server/brand-assets.ts
T
2026-11-14 09:56:45 +00:00

58 lines
3.2 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// Where the brand's logos are read from for a share image and an email: the app brand's from the
// app's own public folder, an organization's through the same caller every page uses (so only a
// member reads it). The branding module decides which one is drawn (`shareBrand`); this file only
// fetches bytes, and the module's kit checks decide whether they are a logo.
import { existsSync } from "node:fs"
import { readFile } from "node:fs/promises"
import { createRequire } from "node:module"
import path from "node:path"
import { type AppBrand, appBrand } from "@app/config"
import { type AppIdentity, logoGet, type ShareReaders } from "@app/modules/branding"
import type { LogoSource } from "@conusai-kit/brand/logo/check"
import type { TransportCaller } from "@conusai-kit/core"
const TYPES: Readonly<Record<string, string>> = { ".png": "image/png", ".webp": "image/webp", ".svg": "image/svg+xml" }
const KIT_FAVICON = "/favicon.png"
const KIT_FAVICON_FILE = "@conusai-kit/brand/logo/favicon.png"
/** The app's public folder: `node server.mjs` and `vite dev` run in apps/web. */
export const publicDirectory = (): string => path.join(process.cwd(), "public")
/** A file of the public folder as a logo source. The path is the app brand's (`defineBrand` keeps it on this origin); it is still held inside the folder. */
export async function appLogoSource(logoPath: string, directory: string = publicDirectory()): Promise<LogoSource> {
const contentType = TYPES[path.extname(logoPath).toLowerCase()]
if (contentType === undefined) throw new Error("the app brand's logo is a PNG, WebP or SVG file")
const root = path.resolve(/*turbopackIgnore: true*/ directory)
const file = path.resolve(/*turbopackIgnore: true*/ root, `.${logoPath}`)
if (!file.startsWith(`${root}${path.sep}`)) throw new Error("the app brand's logo is a file of the public folder")
// The tab icon the build emits (vite.config.ts) is not in the public folder: it is the kit's own file.
const theKits = logoPath === KIT_FAVICON && !existsSync(file)
const source = theKits ? createRequire(import.meta.url).resolve(KIT_FAVICON_FILE) : file
return { contentType, bytes: new Uint8Array(await readFile(/*turbopackIgnore: true*/ source)) }
}
export const appIdentity = (brand: AppBrand = appBrand): AppIdentity => ({
name: brand.name,
logo: { light: brand.logo.light, dark: brand.logo.dark },
})
/** The readers for an organization's page: its own logos through the caller, the app's from the public folder. */
export function readersFor(caller: TransportCaller, org: string, directory?: string): ShareReaders {
return {
appLogo: (logoPath) => appLogoSource(logoPath, directory),
async tenantLogo(variant) {
const logo = await caller.call(logoGet, { org, variant })
return { contentType: logo.contentType, bytes: new Uint8Array(Buffer.from(logo.body, "base64")) }
},
}
}
/** The readers for the app's own card: no organization, so an organization's logo is never asked for. */
export const appOnlyReaders = (directory?: string): ShareReaders => ({
appLogo: (logoPath) => appLogoSource(logoPath, directory),
tenantLogo: () => Promise.reject(new Error("the app's own card has no organization")),
})