From 09233de34052f9c70882b92fbaf1a0fa8fd1ed17 Mon Sep 17 00:00:00 2001 From: bencmbrook <7354176+bencmbrook@users.noreply.github.com> Date: Wed, 27 May 2026 19:29:29 -0400 Subject: [PATCH 01/17] init react-airgap --- packages/react-airgap/package.json | 57 ++ packages/react-airgap/src/index.ts | 11 + .../src/next/tracking-script.test.ts | 79 +++ .../react-airgap/src/next/tracking-script.tsx | 292 +++++++++ packages/react-airgap/tsconfig.json | 14 + packages/react-airgap/tsdown.config.ts | 8 + pnpm-lock.yaml | 564 ++++++++++++++++++ pnpm-workspace.yaml | 7 +- tsconfig.json | 1 + 9 files changed, 1032 insertions(+), 1 deletion(-) create mode 100644 packages/react-airgap/package.json create mode 100644 packages/react-airgap/src/index.ts create mode 100644 packages/react-airgap/src/next/tracking-script.test.ts create mode 100644 packages/react-airgap/src/next/tracking-script.tsx create mode 100644 packages/react-airgap/tsconfig.json create mode 100644 packages/react-airgap/tsdown.config.ts diff --git a/packages/react-airgap/package.json b/packages/react-airgap/package.json new file mode 100644 index 00000000..a29fe23a --- /dev/null +++ b/packages/react-airgap/package.json @@ -0,0 +1,57 @@ +{ + "name": "@transcend-io/react-airgap", + "version": "0.0.0", + "description": "React components for the Transcend Airgap library.", + "homepage": "https://github.com/transcend-io/tools/tree/main/packages/react-airgap", + "license": "Apache-2.0", + "author": "Transcend Inc.", + "repository": { + "type": "git", + "url": "https://github.com/transcend-io/tools.git", + "directory": "packages/react-airgap" + }, + "files": [ + "dist" + ], + "type": "module", + "sideEffects": false, + "types": "./dist/index.d.mts", + "exports": { + ".": { + "@transcend-io/source": "./src/index.ts", + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + } + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsdown", + "test": "vitest run", + "typecheck": "tsc -p tsconfig.json --noEmit", + "check:exports": "attw --pack . --ignore-rules cjs-resolves-to-esm", + "check:publint": "publint --level warning --strict --pack pnpm" + }, + "devDependencies": { + "@arethetypeswrong/cli": "catalog:", + "@types/node": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "next": "catalog:", + "publint": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "peerDependencies": { + "next": "catalog:", + "react": "catalog:", + "react-dom": "catalog:" + }, + "engines": { + "node": ">=22.12.0" + } +} diff --git a/packages/react-airgap/src/index.ts b/packages/react-airgap/src/index.ts new file mode 100644 index 00000000..5494e500 --- /dev/null +++ b/packages/react-airgap/src/index.ts @@ -0,0 +1,11 @@ +'use client'; + +export { + allOf, + anyOf, + default as TrackingScript, + onConsent, + onEvent, + onPromise, +} from './next/tracking-script.js'; +export type { LoadTrigger, OnEventOptions, TrackingScriptProps } from './next/tracking-script.js'; diff --git a/packages/react-airgap/src/next/tracking-script.test.ts b/packages/react-airgap/src/next/tracking-script.test.ts new file mode 100644 index 00000000..63ffed8c --- /dev/null +++ b/packages/react-airgap/src/next/tracking-script.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, test, vi } from 'vitest'; + +import { allOf, anyOf, onEvent, onPromise, type LoadTrigger } from './tracking-script.js'; + +describe('tracking script triggers', () => { + test('onEvent loads once when the event fires', () => { + const target = new EventTarget(); + const load = vi.fn(); + const cleanup = onEvent('ready', { target })(load); + + target.dispatchEvent(new Event('ready')); + target.dispatchEvent(new Event('ready')); + + expect(load).toHaveBeenCalledTimes(1); + cleanup?.(); + }); + + test('onEvent loads immediately when the latch is already open', () => { + const target = new EventTarget(); + const load = vi.fn(); + + onEvent('ready', { target, latch: () => true })(load); + + expect(load).toHaveBeenCalledTimes(1); + }); + + test('onPromise loads when the promise resolves', async () => { + const load = vi.fn(); + + onPromise(Promise.resolve())(load); + await Promise.resolve(); + + expect(load).toHaveBeenCalledTimes(1); + }); + + test('anyOf loads once when the first trigger fires and cleans up every trigger', () => { + const triggerCallbacks: Array<() => void> = []; + const firstCleanup = vi.fn(); + const secondCleanup = vi.fn(); + const first: LoadTrigger = (load) => { + triggerCallbacks[0] = load; + return firstCleanup; + }; + const second: LoadTrigger = (load) => { + triggerCallbacks[1] = load; + return secondCleanup; + }; + const load = vi.fn(); + + const cleanup = anyOf(first, second)(load); + triggerCallbacks[0]?.(); + triggerCallbacks[1]?.(); + cleanup?.(); + + expect(load).toHaveBeenCalledTimes(1); + expect(firstCleanup).toHaveBeenCalledTimes(1); + expect(secondCleanup).toHaveBeenCalledTimes(1); + }); + + test('allOf waits for every trigger and ignores duplicate trigger calls', () => { + const triggerCallbacks: Array<() => void> = []; + const first: LoadTrigger = (load) => { + triggerCallbacks[0] = load; + }; + const second: LoadTrigger = (load) => { + triggerCallbacks[1] = load; + }; + const load = vi.fn(); + + allOf(first, second)(load); + triggerCallbacks[0]?.(); + triggerCallbacks[0]?.(); + expect(load).not.toHaveBeenCalled(); + + triggerCallbacks[1]?.(); + + expect(load).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/react-airgap/src/next/tracking-script.tsx b/packages/react-airgap/src/next/tracking-script.tsx new file mode 100644 index 00000000..da8226ab --- /dev/null +++ b/packages/react-airgap/src/next/tracking-script.tsx @@ -0,0 +1,292 @@ +'use client'; + +/** + * TrackingScript — a drop-in wrapper around `next/script` that defers script + * injection until one or more triggers fire (custom events, promises, or + * Transcend airgap consent). + * + * Supports both forms of `next/script`: + * + * {`...inline JS...`} + * + * SSR: like `; +} + +/* -------------------------------------------------------------------------- */ +/* Examples */ +/* -------------------------------------------------------------------------- */ + +/* +import TrackingScript, { onEvent, onConsent, onPromise, anyOf, allOf } from './tracking-script'; + +// 1. External script gated on a custom event + + +// 2. External script gated on airgap + Analytics consent + + +// 3. Inline GTM bootstrap gated on consent (note: requires `id`) +{` + (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'}); + var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=true; + j.src='https://www.googletagmanager.com/gtm.js?id='+i;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer','GTM-XXXX'); +`} + +// 4. Compound: consent AND (custom event OR 5s timeout) + setTimeout(r, 5000))), + ), + )} +/> +*/ diff --git a/packages/react-airgap/tsconfig.json b/packages/react-airgap/tsconfig.json new file mode 100644 index 00000000..4d330f66 --- /dev/null +++ b/packages/react-airgap/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "outDir": "dist", + "rootDir": "src", + "types": ["node", "vitest/globals"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/packages/react-airgap/tsdown.config.ts b/packages/react-airgap/tsdown.config.ts new file mode 100644 index 00000000..7ab4ad4c --- /dev/null +++ b/packages/react-airgap/tsdown.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'tsdown'; + +import sharedConfig from '../../tsdown.config.base.ts'; + +export default defineConfig({ + ...sharedConfig, + entry: ['src/index.ts'], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad1dee36..2e8fcfec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,12 +21,21 @@ catalogs: '@types/node': specifier: ^22.19.15 version: 22.19.15 + '@types/react': + specifier: ^19.2.15 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3 '@types/semver': specifier: ^7.7.1 version: 7.7.1 husky: specifier: ^9.1.7 version: 9.1.7 + next: + specifier: ^16.2.6 + version: 16.2.6 oxfmt: specifier: ^0.38.0 version: 0.38.0 @@ -36,6 +45,12 @@ catalogs: publint: specifier: ^0.3.18 version: 0.3.18 + react: + specifier: ^19.2.6 + version: 19.2.6 + react-dom: + specifier: ^19.2.6 + version: 19.2.6 semver: specifier: ^7.7.4 version: 7.7.4 @@ -733,6 +748,42 @@ importers: specifier: 'catalog:' version: 4.0.18(@types/node@22.19.15)(tsx@4.21.0) + packages/react-airgap: + devDependencies: + '@arethetypeswrong/cli': + specifier: 'catalog:' + version: 0.18.2 + '@types/node': + specifier: 'catalog:' + version: 22.19.15 + '@types/react': + specifier: 'catalog:' + version: 19.2.15 + '@types/react-dom': + specifier: 'catalog:' + version: 19.2.3(@types/react@19.2.15) + next: + specifier: 'catalog:' + version: 16.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + publint: + specifier: 'catalog:' + version: 0.3.18 + react: + specifier: 'catalog:' + version: 19.2.6 + react-dom: + specifier: 'catalog:' + version: 19.2.6(react@19.2.6) + tsdown: + specifier: 'catalog:' + version: 0.21.2(@arethetypeswrong/core@0.18.2)(publint@0.3.18)(typescript@6.0.3) + typescript: + specifier: 'catalog:' + version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@22.19.15)(tsx@4.21.0) + packages/sdk: dependencies: '@transcend-io/airgap.js-types': @@ -1182,6 +1233,159 @@ packages: peerDependencies: hono: ^4 + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -1229,6 +1433,61 @@ packages: '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@next/env@16.2.6': + resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} + + '@next/swc-darwin-arm64@16.2.6': + resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.2.6': + resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.2.6': + resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-musl@16.2.6': + resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@next/swc-linux-x64-gnu@16.2.6': + resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-musl@16.2.6': + resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@next/swc-win32-arm64-msvc@16.2.6': + resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.2.6': + resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1752,6 +2011,9 @@ packages: '@stricli/core@1.2.6': resolution: {integrity: sha512-j5fa1wyOLrP9WJqqLFEJeQviqb3cK46K+FXTuISEkG/H5C820YfKDoVQ3CDVdM5WLhEx1ARdpiW6+hU4tYHjCQ==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@textlint/ast-node-types@12.6.1': resolution: {integrity: sha512-uzlJ+ZsCAyJm+lBi7j0UeBbj+Oy6w/VWoGJ3iHRHE5eZ8Z4iK66mq+PG/spupmbllLtz77OJbY89BYqgFyjXmA==} @@ -1900,6 +2162,14 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.15': + resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -2043,6 +2313,11 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.32: + resolution: {integrity: sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==} + engines: {node: '>=6.0.0'} + hasBin: true + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -2109,6 +2384,9 @@ packages: camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -2178,6 +2456,9 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -2234,6 +2515,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + csv-parse@5.6.0: resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} @@ -2295,6 +2579,10 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -3197,6 +3485,27 @@ packages: fp-ts: ^2.0.0 monocle-ts: ^2.0.0 + next@16.2.6: + resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -3369,6 +3678,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -3416,6 +3729,15 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -3554,6 +3876,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -3592,6 +3917,10 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3703,6 +4032,19 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4488,6 +4830,103 @@ snapshots: dependencies: hono: 4.12.12 + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@inquirer/external-editor@1.0.3(@types/node@22.19.15)': dependencies: chardet: 2.1.1 @@ -4560,6 +4999,32 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@next/env@16.2.6': {} + + '@next/swc-darwin-arm64@16.2.6': + optional: true + + '@next/swc-darwin-x64@16.2.6': + optional: true + + '@next/swc-linux-arm64-gnu@16.2.6': + optional: true + + '@next/swc-linux-arm64-musl@16.2.6': + optional: true + + '@next/swc-linux-x64-gnu@16.2.6': + optional: true + + '@next/swc-linux-x64-musl@16.2.6': + optional: true + + '@next/swc-win32-arm64-msvc@16.2.6': + optional: true + + '@next/swc-win32-x64-msvc@16.2.6': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4832,6 +5297,10 @@ snapshots: '@stricli/core@1.2.6': {} + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + '@textlint/ast-node-types@12.6.1': {} '@textlint/markdown-to-ast@12.6.1': @@ -5002,6 +5471,14 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/react-dom@19.2.3(@types/react@19.2.15)': + dependencies: + '@types/react': 19.2.15 + + '@types/react@19.2.15': + dependencies: + csstype: 3.2.3 + '@types/semver@7.7.1': {} '@types/send@1.2.1': @@ -5151,6 +5628,8 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.10.32: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -5232,6 +5711,8 @@ snapshots: pascal-case: 3.1.2 tslib: 2.8.1 + caniuse-lite@1.0.30001793: {} + capital-case@1.0.4: dependencies: no-case: 3.0.4 @@ -5305,6 +5786,8 @@ snapshots: cli-width@3.0.0: {} + client-only@0.0.1: {} + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -5358,6 +5841,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + csstype@3.2.3: {} + csv-parse@5.6.0: {} data-view-buffer@1.0.2: @@ -5414,6 +5899,9 @@ snapshots: detect-indent@6.1.0: {} + detect-libc@2.1.2: + optional: true + detect-node@2.1.0: {} dir-glob@3.0.1: @@ -6457,6 +6945,30 @@ snapshots: fp-ts: 2.16.11 monocle-ts: 2.3.13(fp-ts@2.16.11) + next@16.2.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@next/env': 16.2.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.32 + caniuse-lite: 1.0.30001793 + postcss: 8.4.31 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + styled-jsx: 5.1.6(react@19.2.6) + optionalDependencies: + '@next/swc-darwin-arm64': 16.2.6 + '@next/swc-darwin-x64': 16.2.6 + '@next/swc-linux-arm64-gnu': 16.2.6 + '@next/swc-linux-arm64-musl': 16.2.6 + '@next/swc-linux-x64-gnu': 16.2.6 + '@next/swc-linux-x64-musl': 16.2.6 + '@next/swc-win32-arm64-msvc': 16.2.6 + '@next/swc-win32-x64-msvc': 16.2.6 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -6650,6 +7162,12 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -6698,6 +7216,13 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react@19.2.6: {} + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -6910,6 +7435,8 @@ snapshots: safer-buffer@2.1.2: {} + scheduler@0.27.0: {} + semver-compare@1.0.0: {} semver@7.7.4: {} @@ -6973,6 +7500,38 @@ snapshots: setprototypeof@1.2.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -7091,6 +7650,11 @@ snapshots: strip-bom@3.0.0: {} + styled-jsx@5.1.6(react@19.2.6): + dependencies: + client-only: 0.0.1 + react: 19.2.6 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c586b3e2..8de26747 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,11 +9,16 @@ catalog: '@transcend-io/internationalization': ^2.3.2 '@types/json-schema': ^7.0.15 '@types/node': ^22.19.15 + '@types/react': ^19.2.15 + '@types/react-dom': ^19.2.3 '@types/semver': ^7.7.1 husky: ^9.1.7 + next: ^16.2.6 oxfmt: ^0.38.0 oxlint: ^1.53.0 publint: ^0.3.18 + react: ^19.2.6 + react-dom: ^19.2.6 semver: ^7.7.4 syncpack: ^14.2.0 tsdown: ^0.21.2 @@ -22,8 +27,8 @@ catalog: vitest: ^4.0.18 zod: ^4.3.6 -# Only install npm packages that are at least 3 days old minimumReleaseAge: 4320 + minimumReleaseAgeExclude: - '@transcend-io/*' diff --git a/tsconfig.json b/tsconfig.json index 99f34d37..39e47e0f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ { "path": "./packages/mcp/mcp-server-preferences" }, { "path": "./packages/mcp/mcp-server-workflows" }, { "path": "./packages/privacy-types" }, + { "path": "./packages/react-airgap" }, { "path": "./packages/sdk" }, { "path": "./packages/type-utils" }, { "path": "./packages/utils" } From 3626887f5fdc35f4355d4972940bc43d093343b4 Mon Sep 17 00:00:00 2001 From: bencmbrook <7354176+bencmbrook@users.noreply.github.com> Date: Wed, 27 May 2026 19:43:50 -0400 Subject: [PATCH 02/17] Simplify react-airgap script gating --- packages/react-airgap/src/index.ts | 11 +- .../src/next/tracking-script.test.ts | 96 ++---- .../react-airgap/src/next/tracking-script.tsx | 287 +++--------------- 3 files changed, 73 insertions(+), 321 deletions(-) diff --git a/packages/react-airgap/src/index.ts b/packages/react-airgap/src/index.ts index 5494e500..5610c864 100644 --- a/packages/react-airgap/src/index.ts +++ b/packages/react-airgap/src/index.ts @@ -1,11 +1,4 @@ 'use client'; -export { - allOf, - anyOf, - default as TrackingScript, - onConsent, - onEvent, - onPromise, -} from './next/tracking-script.js'; -export type { LoadTrigger, OnEventOptions, TrackingScriptProps } from './next/tracking-script.js'; +export { default as TrackingScript } from './next/tracking-script.js'; +export type { LoadAfter, TrackingScriptProps } from './next/tracking-script.js'; diff --git a/packages/react-airgap/src/next/tracking-script.test.ts b/packages/react-airgap/src/next/tracking-script.test.ts index 63ffed8c..22a8f079 100644 --- a/packages/react-airgap/src/next/tracking-script.test.ts +++ b/packages/react-airgap/src/next/tracking-script.test.ts @@ -1,79 +1,43 @@ -import { describe, expect, test, vi } from 'vitest'; +import { describe, expect, test } from 'vitest'; -import { allOf, anyOf, onEvent, onPromise, type LoadTrigger } from './tracking-script.js'; +import { waitForLoadAfter } from './tracking-script.js'; -describe('tracking script triggers', () => { - test('onEvent loads once when the event fires', () => { - const target = new EventTarget(); - const load = vi.fn(); - const cleanup = onEvent('ready', { target })(load); +describe('waitForLoadAfter', () => { + test('resolves after a single promise resolves', async () => { + const value = {}; - target.dispatchEvent(new Event('ready')); - target.dispatchEvent(new Event('ready')); + const result = await waitForLoadAfter(Promise.resolve(value)); - expect(load).toHaveBeenCalledTimes(1); - cleanup?.(); + expect(result).toBe(value); }); - test('onEvent loads immediately when the latch is already open', () => { - const target = new EventTarget(); - const load = vi.fn(); - - onEvent('ready', { target, latch: () => true })(load); - - expect(load).toHaveBeenCalledTimes(1); - }); - - test('onPromise loads when the promise resolves', async () => { - const load = vi.fn(); - - onPromise(Promise.resolve())(load); + test('waits for promise composition from the caller', async () => { + let resolveFirst!: () => void; + let resolveSecond!: () => void; + const first = new Promise((resolve) => { + resolveFirst = resolve; + }); + const second = new Promise((resolve) => { + resolveSecond = resolve; + }); + let resolved = false; + + void waitForLoadAfter(Promise.all([first, second])).then(() => { + resolved = true; + }); + resolveFirst(); await Promise.resolve(); + expect(resolved).toBe(false); - expect(load).toHaveBeenCalledTimes(1); - }); - - test('anyOf loads once when the first trigger fires and cleans up every trigger', () => { - const triggerCallbacks: Array<() => void> = []; - const firstCleanup = vi.fn(); - const secondCleanup = vi.fn(); - const first: LoadTrigger = (load) => { - triggerCallbacks[0] = load; - return firstCleanup; - }; - const second: LoadTrigger = (load) => { - triggerCallbacks[1] = load; - return secondCleanup; - }; - const load = vi.fn(); - - const cleanup = anyOf(first, second)(load); - triggerCallbacks[0]?.(); - triggerCallbacks[1]?.(); - cleanup?.(); - - expect(load).toHaveBeenCalledTimes(1); - expect(firstCleanup).toHaveBeenCalledTimes(1); - expect(secondCleanup).toHaveBeenCalledTimes(1); + resolveSecond(); + await Promise.all([first, second]); + await Promise.resolve(); + expect(resolved).toBe(true); }); - test('allOf waits for every trigger and ignores duplicate trigger calls', () => { - const triggerCallbacks: Array<() => void> = []; - const first: LoadTrigger = (load) => { - triggerCallbacks[0] = load; - }; - const second: LoadTrigger = (load) => { - triggerCallbacks[1] = load; - }; - const load = vi.fn(); - - allOf(first, second)(load); - triggerCallbacks[0]?.(); - triggerCallbacks[0]?.(); - expect(load).not.toHaveBeenCalled(); - - triggerCallbacks[1]?.(); + test('rejects when a load promise rejects', async () => { + const error = new Error('gate failed'); - expect(load).toHaveBeenCalledTimes(1); + await expect(waitForLoadAfter(Promise.reject(error))).rejects.toThrow(error); }); }); diff --git a/packages/react-airgap/src/next/tracking-script.tsx b/packages/react-airgap/src/next/tracking-script.tsx index da8226ab..2d14b95c 100644 --- a/packages/react-airgap/src/next/tracking-script.tsx +++ b/packages/react-airgap/src/next/tracking-script.tsx @@ -1,198 +1,57 @@ 'use client'; -/** - * TrackingScript — a drop-in wrapper around `next/script` that defers script - * injection until one or more triggers fire (custom events, promises, or - * Transcend airgap consent). - * - * Supports both forms of `next/script`: - * - * {`...inline JS...`} - * - * SSR: like `; } - -/* -------------------------------------------------------------------------- */ -/* Examples */ -/* -------------------------------------------------------------------------- */ - -/* -import TrackingScript, { onEvent, onConsent, onPromise, anyOf, allOf } from './tracking-script'; - -// 1. External script gated on a custom event - - -// 2. External script gated on airgap + Analytics consent - - -// 3. Inline GTM bootstrap gated on consent (note: requires `id`) -{` - (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'}); - var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=true; - j.src='https://www.googletagmanager.com/gtm.js?id='+i;f.parentNode.insertBefore(j,f); - })(window,document,'script','dataLayer','GTM-XXXX'); -`} - -// 4. Compound: consent AND (custom event OR 5s timeout) - setTimeout(r, 5000))), - ), - )} -/> -*/ From 2776c3b32e379d7bb1e2a24e590e219aec1e4ed0 Mon Sep 17 00:00:00 2001 From: bencmbrook <7354176+bencmbrook@users.noreply.github.com> Date: Wed, 27 May 2026 19:55:59 -0400 Subject: [PATCH 03/17] airgapReady --- packages/react-airgap/README.md | 85 +++++++++++++++++++ packages/react-airgap/src/index.ts | 1 + .../src/next/airgap-ready.test.ts | 51 +++++++++++ .../react-airgap/src/next/airgap-ready.ts | 42 +++++++++ .../react-airgap/src/next/tracking-script.tsx | 8 +- 5 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 packages/react-airgap/README.md create mode 100644 packages/react-airgap/src/next/airgap-ready.test.ts create mode 100644 packages/react-airgap/src/next/airgap-ready.ts diff --git a/packages/react-airgap/README.md b/packages/react-airgap/README.md new file mode 100644 index 00000000..77fd20b2 --- /dev/null +++ b/packages/react-airgap/README.md @@ -0,0 +1,85 @@ +# @transcend-io/react-airgap + +React helpers for loading scripts after Transcend Airgap is ready. + +## TrackingScript + +`TrackingScript` is a small client component around `next/script`. It renders +nothing until its `loadAfter` promise resolves, then renders the underlying +`