diff --git a/apps/web/app.config.ts b/apps/web/app.config.ts index 1180e6eb..93ab5689 100644 --- a/apps/web/app.config.ts +++ b/apps/web/app.config.ts @@ -16,11 +16,7 @@ export default defineConfig({ // @ts-expect-error: SS's types are wrong. This is piped into Solid's Vite plugin so all options are not required. solid: { // We don't wanna apply Solid's JSX transform to the React emails. - exclude: [ - "src/emails/*", - "src/components/OTPInput/react.tsx", - "../../packages/email/**", - ], + exclude: ["src/components/OTPInput/react.tsx", "../../packages/email/**"], }, vite: ({ router }) => ({ envDir: monorepoRoot, @@ -59,6 +55,9 @@ export default defineConfig({ esbuild: { options: { target: "es2020" }, }, + analyze: { + filename: "stats-nitro.html", + }, }, }); diff --git a/apps/web/src/api/rest/webhook/microsoft-graph.ts b/apps/web/src/api/rest/webhook/microsoft-graph.ts index 78eb070d..664a06e2 100644 --- a/apps/web/src/api/rest/webhook/microsoft-graph.ts +++ b/apps/web/src/api/rest/webhook/microsoft-graph.ts @@ -4,7 +4,6 @@ import { and, eq } from "drizzle-orm"; import { Hono } from "hono"; import { z } from "zod"; -import { isNotFoundGraphError } from "@mattrax/ms-graph"; import { msGraphClient } from "~/api/microsoft"; import { upsertEntraIdUser } from "~/api/trpc/routers/tenant/identityProvider"; import { getEmailDomain } from "~/api/utils"; @@ -152,8 +151,8 @@ async function handleUserChangeNotification( identityProvider.tenantPk, identityProvider.pk, ); - } catch (e) { - if (isNotFoundGraphError(e)) { + } catch (e: any) { + if ("code" in e && e.code === "Request_ResourceNotFound") { await handleUserDeleted(userId, identityProvider.pk); } } diff --git a/apps/web/src/api/trpc/routers/tenant/identityProvider.ts b/apps/web/src/api/trpc/routers/tenant/identityProvider.ts index 80b8abb5..a64ee0ef 100644 --- a/apps/web/src/api/trpc/routers/tenant/identityProvider.ts +++ b/apps/web/src/api/trpc/routers/tenant/identityProvider.ts @@ -3,7 +3,6 @@ import { TRPCError } from "@trpc/server"; import { and, eq } from "drizzle-orm"; import { z } from "zod"; -import { msGraphClient } from "~/api/microsoft"; import { getEmailDomain } from "~/api/utils"; import { domains, db, identityProviders, users } from "~/db"; import { createTRPCRouter, tenantProcedure } from "../../helpers"; @@ -45,6 +44,8 @@ export const identityProviderRouter = createTRPCRouter({ // We ignore any errors cleaning up the subscriptions cause it's a non vital error. try { + const { msGraphClient } = await import("~/api/microsoft"); + const subscriptions: { value: Array } = await msGraphClient(provider.remoteId).api("/subscriptions").get(); @@ -84,7 +85,7 @@ export const identityProviderRouter = createTRPCRouter({ let identityProvider!: IdentityProvider; if (provider.provider === "entraId") - identityProvider = createEntraIDUserProvider(provider.remoteId); + identityProvider = await createEntraIDUserProvider(provider.remoteId); const [remoteDomains, connectedDomains] = await Promise.all([ identityProvider.getDomains(), @@ -108,7 +109,7 @@ export const identityProviderRouter = createTRPCRouter({ let identityProvider!: IdentityProvider; if (provider.provider === "entraId") - identityProvider = createEntraIDUserProvider(provider.remoteId); + identityProvider = await createEntraIDUserProvider(provider.remoteId); const remoteDomains = await identityProvider.getDomains(); if (!remoteDomains.includes(input.domain)) @@ -224,9 +225,10 @@ async function ensureIdentityProvider(tenantPk: number) { return provider; } -export function createEntraIDUserProvider( +export async function createEntraIDUserProvider( resourceId: string, -): IdentityProvider { +): Promise { + const { msGraphClient } = await import("~/api/microsoft"); const client = msGraphClient(resourceId); return { @@ -282,6 +284,7 @@ export async function syncEntraUsersWithDomains( entraTenantId: string, domains: string[], ) { + const { msGraphClient } = await import("~/api/microsoft"); const graphClient = msGraphClient(entraTenantId); let response: { diff --git a/apps/web/src/api/utils/jwt.ts b/apps/web/src/api/utils/jwt.ts index a3536267..dbe1ae0c 100644 --- a/apps/web/src/api/utils/jwt.ts +++ b/apps/web/src/api/utils/jwt.ts @@ -1,4 +1,4 @@ -import * as jose from "jose"; +import type * as jose from "jose"; import { env } from "~/env"; export async function signJWT( @@ -9,6 +9,8 @@ export async function signJWT( audience?: string; }, ) { + const jose = await import("jose"); + const builder = new jose.SignJWT(payload) .setAudience(opts?.audience ?? "mattrax.app") .setNotBefore(new Date()); @@ -22,6 +24,7 @@ export async function signJWT( } export async function verifyJWT(jwt: string) { + const jose = await import("jose"); const result = await jose.jwtVerify(jwt, createSecretKey()); return result.payload; } @@ -34,6 +37,7 @@ export async function encryptJWT( audience?: string; }, ) { + const jose = await import("jose"); const builder = new jose.EncryptJWT(payload) .setAudience(opts?.audience ?? "mattrax.app") .setNotBefore(new Date()); @@ -56,6 +60,7 @@ export async function decryptJWT( audience?: string; }, ) { + const jose = await import("jose"); return await jose.jwtDecrypt(jwt, createSecretKey(), { keyManagementAlgorithms: ["PBES2-HS256+A128KW"], }); diff --git a/apps/web/src/app/(dash)/o.[orgSlug]/utils.ts b/apps/web/src/app/(dash)/o.[orgSlug]/utils.ts index 79c10632..c0705787 100644 --- a/apps/web/src/app/(dash)/o.[orgSlug]/utils.ts +++ b/apps/web/src/app/(dash)/o.[orgSlug]/utils.ts @@ -1,11 +1,13 @@ import { cache } from "@solidjs/router"; -import { mattraxCache } from "~/cache"; +import { getMattraxCache } from "~/cache"; export const cachedTenantsForOrg = cache( (orgId: string) => - mattraxCache.tenants - .orderBy("id") - .filter((o) => o.orgId === orgId) - .toArray(), + getMattraxCache().then((c) => + c.tenants + .orderBy("id") + .filter((o) => o.orgId === orgId) + .toArray(), + ), "cachedTenantsForOrg", ); diff --git a/apps/web/src/app/(dash)/utils.ts b/apps/web/src/app/(dash)/utils.ts index c3d72a36..8256cc74 100644 --- a/apps/web/src/app/(dash)/utils.ts +++ b/apps/web/src/app/(dash)/utils.ts @@ -1,8 +1,8 @@ import { cache } from "@solidjs/router"; -import { mattraxCache } from "~/cache"; +import { getMattraxCache } from "~/cache"; // can't be in a route file that consumes it export const cachedOrgs = cache( - () => mattraxCache.orgs.orderBy("id").toArray(), + () => getMattraxCache().then((c) => c.orgs.orderBy("id").toArray()), "cachedOrgs", ); diff --git a/apps/web/src/cache/dexie.ts b/apps/web/src/cache/dexie.ts new file mode 100644 index 00000000..7865fb18 --- /dev/null +++ b/apps/web/src/cache/dexie.ts @@ -0,0 +1,34 @@ +import Dexie from "dexie"; + +export type TableNames = "orgs" | "tenants"; + +class MattraxCache + extends Dexie + implements Record> +{ + orgs!: Dexie.Table<{ id: string; slug: string; name: string }, string>; + tenants!: Dexie.Table< + { id: string; slug: string; name: string; orgId: string }, + string + >; + + VERSION = 1; + + constructor() { + super("mattrax-cache"); + this.version(this.VERSION).stores({ + orgs: "id", + tenants: "id, orgId", + }); + } +} + +export type { MattraxCache }; + +export const mattraxCache = new MattraxCache(); + +export type TableData = TTable extends Dexie.Table< + infer T +> + ? T + : never; diff --git a/apps/web/src/cache/index.ts b/apps/web/src/cache/index.ts index 50534cee..6c286ae0 100644 --- a/apps/web/src/cache/index.ts +++ b/apps/web/src/cache/index.ts @@ -1,43 +1,16 @@ import { createAsync } from "@solidjs/router"; import type { CreateQueryResult } from "@tanstack/solid-query"; -import Dexie from "dexie"; import { type Accessor, createEffect, untrack } from "solid-js"; +import { MattraxCache, TableData, TableNames } from "./dexie"; +export type { TableData, TableNames, MattraxCache } from "./dexie"; -type TableNames = "orgs" | "tenants"; - -class MattraxCache - extends Dexie - implements Record> -{ - orgs!: Dexie.Table<{ id: string; slug: string; name: string }, string>; - tenants!: Dexie.Table< - { id: string; slug: string; name: string; orgId: string }, - string - >; - - VERSION = 1; - - constructor() { - super("mattrax-cache"); - this.version(this.VERSION).stores({ - orgs: "id", - tenants: "id, orgId", - }); - } -} - -export type { MattraxCache }; - -export type TableData = TTable extends Dexie.Table< - infer T -> - ? T - : never; - -export const mattraxCache = new MattraxCache(); +export const getMattraxCache = () => + import("./dexie").then((m) => m.mattraxCache); export async function resetMattraxCache() { + const mattraxCache = await getMattraxCache(); await mattraxCache.delete(); + await mattraxCache.open(); } @@ -46,9 +19,13 @@ export function createQueryCacher( table: TTable, transform: (data: TData) => TableData, ) { + const mattraxCache = createAsync(() => getMattraxCache()); + createEffect(() => { if (!query.data) return; - mattraxCache[table].bulkPut(query.data.map(transform) as any); + const cache = mattraxCache(); + if (!cache) return; + cache[table].bulkPut(query.data.map(transform) as any); }); } diff --git a/packages/ms-graph/src/index.ts b/packages/ms-graph/src/index.ts index 5e6566e3..874d77d3 100644 --- a/packages/ms-graph/src/index.ts +++ b/packages/ms-graph/src/index.ts @@ -1,4 +1,4 @@ -import { Client, GraphError } from "@microsoft/microsoft-graph-client"; +import { Client } from "@microsoft/microsoft-graph-client"; import { AuthProvider } from "./authProvider"; // Unused but makes TS happy. @@ -8,14 +8,15 @@ export const initGraphClient = ( tenantId: string, clientId: string, clientSecret: string, -) => - Client.initWithMiddleware({ +) => { + return Client.initWithMiddleware({ authProvider: new AuthProvider(tenantId, clientId, clientSecret), }); +}; -export function isGraphError(err: any): err is GraphError { - return err instanceof GraphError; -} -export function isNotFoundGraphError(err: any): err is GraphError { - return isGraphError(err) && err.code === "Request_ResourceNotFound"; -} +// export function isGraphError(err: any): err is GraphError { +// return err instanceof GraphError; +// } +// export function isNotFoundGraphError(err: any): err is GraphError { +// return isGraphError(err) && err.code === "Request_ResourceNotFound"; +// }