auramaxx 1.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,428 @@
1
+ 'use client';
2
+
3
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { Box, Loader2 } from 'lucide-react';
5
+ import { APP_SDK_SOURCE } from '@/lib/app-sdk';
6
+ import { useAuth } from '@/context/AuthContext';
7
+ import { useWebSocket } from '@/context/WebSocketContext';
8
+ import { api, Api } from '@/lib/api';
9
+
10
+ interface ThirdPartyAppProps {
11
+ config?: {
12
+ appPath?: string;
13
+ appName?: string;
14
+ _refreshKey?: number;
15
+ };
16
+ }
17
+
18
+ interface AppManifestSummary {
19
+ id: string;
20
+ name: string;
21
+ permissions: string[];
22
+ }
23
+
24
+ interface AppManifestsResponse {
25
+ success: boolean;
26
+ manifests?: AppManifestSummary[];
27
+ }
28
+
29
+ /**
30
+ * Collect CSS variables from the current document for theme injection.
31
+ */
32
+ function collectCssVariables(): string {
33
+ const vars: string[] = [];
34
+ const computed = getComputedStyle(document.documentElement);
35
+ const varNames = [
36
+ '--color-background', '--color-background-alt', '--color-surface',
37
+ '--color-surface-alt', '--color-text', '--color-text-muted',
38
+ '--color-text-faint', '--color-border', '--color-border-muted',
39
+ '--color-border-focus', '--color-accent', '--color-info',
40
+ '--color-success', '--color-warning',
41
+ ];
42
+ for (const name of varNames) {
43
+ const value = computed.getPropertyValue(name).trim();
44
+ if (value) vars.push(`${name}: ${value};`);
45
+ }
46
+ return `:root { ${vars.join(' ')} }`;
47
+ }
48
+
49
+ /**
50
+ * Build a default HTML page for headless apps (no index.html).
51
+ */
52
+ function buildDefaultHtml(appName: string): string {
53
+ const safeName = appName.replace(/</g, '&lt;').replace(/>/g, '&gt;');
54
+ return `<body>
55
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;
56
+ height:100vh;font-family:ui-monospace,monospace;color:var(--color-text-muted,#6b7280);">
57
+ <div style="font-size:10px;letter-spacing:0.1em;text-transform:uppercase;">${safeName}</div>
58
+ <div style="font-size:8px;margin-top:4px;opacity:0.6;">HEADLESS APP — NO UI</div>
59
+ </div>
60
+ </body>`;
61
+ }
62
+
63
+ /**
64
+ * Build self-contained sandboxed HTML by inlining the app's index.html content.
65
+ * The app HTML is fetched on the HOST side (React), then baked into a blob URL.
66
+ * This avoids fetch() inside the sandbox (which has no origin to resolve against).
67
+ *
68
+ * Injects __AURA_TOKEN__, __AURA_API_BASE__, __AURA_APP_ID__ globals
69
+ * so the SDK can make direct fetch() calls to the wallet API.
70
+ */
71
+ function buildAppHtml(
72
+ appHtml: string,
73
+ themeStyles: string,
74
+ appId: string,
75
+ token: string | null,
76
+ ): string {
77
+ // Parse the app's HTML to extract styles, body, and scripts
78
+ const parser = new DOMParser();
79
+ const doc = parser.parseFromString(appHtml, 'text/html');
80
+
81
+ // Collect <style> tags from app
82
+ const appStyles: string[] = [];
83
+ doc.querySelectorAll('style').forEach(s => {
84
+ appStyles.push(s.textContent || '');
85
+ });
86
+
87
+ // Collect inline scripts (skip external src scripts — they can't load in sandbox)
88
+ const appScripts: string[] = [];
89
+ doc.querySelectorAll('script').forEach(s => {
90
+ if (!s.src && s.textContent) {
91
+ // Escape </script to prevent breaking out of our wrapper
92
+ appScripts.push(s.textContent.replace(/<\/script/gi, '<\\/script'));
93
+ }
94
+ });
95
+
96
+ // Remove script tags from body so they aren't included twice
97
+ // (once in bodyContent and once in appScripts)
98
+ doc.querySelectorAll('script').forEach(s => s.remove());
99
+ const bodyContent = doc.body.innerHTML;
100
+
101
+ // Escape token for safe embedding in script
102
+ const safeToken = token ? token.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : '';
103
+
104
+ return `<!DOCTYPE html>
105
+ <html>
106
+ <head>
107
+ <meta charset="utf-8">
108
+ <meta name="viewport" content="width=device-width, initial-scale=1">
109
+ <style>${themeStyles}</style>
110
+ <style>
111
+ * { margin: 0; padding: 0; box-sizing: border-box; }
112
+ body { font-family: ui-monospace, monospace; overflow: auto; background: var(--color-surface, #fff); color: var(--color-text, #0a0a0a); }
113
+ </style>
114
+ ${appStyles.map(s => `<style>${s}</style>`).join('\n')}
115
+ <script>
116
+ window.__AURA_TOKEN__ = "${safeToken}";
117
+ window.__AURA_API_BASE__ = "${api.getBaseUrl(Api.Wallet)}";
118
+ window.__AURA_APP_ID__ = "${appId}";
119
+ <\/script>
120
+ <script>${APP_SDK_SOURCE}<\/script>
121
+ </head>
122
+ <body>
123
+ ${bodyContent}
124
+ ${appScripts.map(s => `<script>${s}<\/script>`).join('\n')}
125
+ </body>
126
+ </html>`;
127
+ }
128
+
129
+ export default function ThirdPartyApp({ config }: ThirdPartyAppProps) {
130
+ const appPath = config?.appPath;
131
+ const refreshKey = config?._refreshKey;
132
+ const iframeRef = useRef<HTMLIFrameElement>(null);
133
+ const [blobUrl, setBlobUrl] = useState<string | null>(null);
134
+ const [loading, setLoading] = useState(false);
135
+ const [error, setError] = useState<string | null>(null);
136
+ const [approvalRequired, setApprovalRequired] = useState(false);
137
+ const [approvalLoading, setApprovalLoading] = useState(false);
138
+ const [approvalError, setApprovalError] = useState<string | null>(null);
139
+ const [approvalDetailsLoading, setApprovalDetailsLoading] = useState(false);
140
+ const [approvalDetails, setApprovalDetails] = useState<AppManifestSummary | null>(null);
141
+ const [approvalRefreshKey, setApprovalRefreshKey] = useState(0);
142
+ const { token: authToken } = useAuth();
143
+ const { subscribe } = useWebSocket();
144
+
145
+ // Track active WS subscriptions so we can unsubscribe on unmount
146
+ const subscriptionsRef = useRef<Map<string, () => void>>(new Map());
147
+
148
+ // Helper: forward a WS event to the iframe as app:data postMessage
149
+ const forwardToIframe = useCallback((channel: string, data: unknown) => {
150
+ iframeRef.current?.contentWindow?.postMessage(
151
+ { type: 'app:data', channel, data },
152
+ '*',
153
+ );
154
+ }, []);
155
+
156
+ // Auto-subscribe to app:emit for this app's custom events
157
+ useEffect(() => {
158
+ if (!appPath) return;
159
+ const unsub = subscribe('app:emit' as any, (event: any) => {
160
+ const d = event.data;
161
+ if (d?.strategyId === appPath && d?.channel) {
162
+ forwardToIframe(d.channel, d.data);
163
+ }
164
+ });
165
+ return unsub;
166
+ }, [appPath, subscribe, forwardToIframe]);
167
+
168
+ // Backward compat: still forward strategy:tick as strategy:state postMessage
169
+ useEffect(() => {
170
+ if (!appPath) return;
171
+ return subscribe('strategy:tick' as any, (event: any) => {
172
+ const data = event.data;
173
+ if (data?.strategyId === appPath && data?.state) {
174
+ iframeRef.current?.contentWindow?.postMessage(
175
+ { type: 'strategy:state', state: data.state },
176
+ '*',
177
+ );
178
+ }
179
+ });
180
+ }, [appPath, subscribe]);
181
+
182
+ // Handle postMessage from the app iframe
183
+ const handleMessage = useCallback((event: MessageEvent) => {
184
+ const msg = event.data;
185
+ if (!msg || !msg.type) return;
186
+
187
+ // Only handle messages from our iframe
188
+ const iframe = iframeRef.current;
189
+ if (!iframe || event.source !== iframe.contentWindow) return;
190
+
191
+ // Process app:open-url — iframe wants to open a link in the parent context
192
+ if (msg.type === 'app:open-url' && typeof msg.url === 'string') {
193
+ window.open(msg.url, '_blank', 'noopener');
194
+ return;
195
+ }
196
+
197
+ // Process app:subscribe — iframe wants to receive a WS event channel
198
+ if (msg.type === 'app:subscribe' && typeof msg.channel === 'string') {
199
+ const channel: string = msg.channel;
200
+ // Skip if already subscribed to this channel
201
+ if (subscriptionsRef.current.has(channel)) return;
202
+
203
+ const unsub = subscribe(channel as any, (wsEvent: any) => {
204
+ forwardToIframe(channel, wsEvent.data);
205
+ });
206
+ subscriptionsRef.current.set(channel, unsub);
207
+ }
208
+ }, [subscribe, forwardToIframe]);
209
+
210
+ // Listen for postMessage from app iframe
211
+ useEffect(() => {
212
+ window.addEventListener('message', handleMessage);
213
+ return () => window.removeEventListener('message', handleMessage);
214
+ }, [handleMessage]);
215
+
216
+ // Cleanup all WS subscriptions on unmount
217
+ useEffect(() => {
218
+ const subs = subscriptionsRef.current;
219
+ return () => {
220
+ for (const unsub of subs.values()) {
221
+ unsub();
222
+ }
223
+ subs.clear();
224
+ };
225
+ }, []);
226
+
227
+ // Fetch app HTML and token on host side, build blob URL
228
+ useEffect(() => {
229
+ if (!appPath) return;
230
+
231
+ let revoked = false;
232
+ let url: string | null = null;
233
+
234
+ setLoading(true);
235
+ setError(null);
236
+ setApprovalRequired(false);
237
+ setApprovalError(null);
238
+ setApprovalDetailsLoading(false);
239
+ setApprovalDetails(null);
240
+
241
+ (async () => {
242
+ const loadApprovalDetails = async () => {
243
+ if (revoked) return;
244
+ setApprovalDetailsLoading(true);
245
+ try {
246
+ const manifestsRes = await fetch('/api/apps/manifests');
247
+ if (!manifestsRes.ok) return;
248
+ const manifestsData = await manifestsRes.json() as AppManifestsResponse;
249
+ if (!manifestsData.success || !Array.isArray(manifestsData.manifests)) return;
250
+ if (revoked) return;
251
+ const match = manifestsData.manifests.find((m) => m.id === appPath);
252
+ if (!match) return;
253
+ const permissions = Array.isArray(match.permissions)
254
+ ? match.permissions.filter((perm) => typeof perm === 'string')
255
+ : [];
256
+ setApprovalDetails({
257
+ id: match.id,
258
+ name: match.name || match.id,
259
+ permissions,
260
+ });
261
+ } catch {
262
+ // Keep approval card usable even if manifest metadata fails to load
263
+ } finally {
264
+ if (!revoked) setApprovalDetailsLoading(false);
265
+ }
266
+ };
267
+
268
+ try {
269
+ const htmlRes = await fetch(`/api/apps/static/${appPath}/index.html`);
270
+ const html = htmlRes.ok
271
+ ? await htmlRes.text()
272
+ : buildDefaultHtml(config?.appName || appPath);
273
+
274
+ let tokenRes: { success: boolean; token?: string };
275
+ try {
276
+ tokenRes = await api.get<{ success: boolean; token?: string }>(
277
+ Api.Wallet, `/apps/${appPath}/token`
278
+ );
279
+ } catch (err) {
280
+ const status = (err as Error & { status?: number }).status;
281
+ const message = err instanceof Error ? err.message : 'Failed to fetch app token';
282
+ if (status === 404 || message.includes('No token for app')) {
283
+ if (!revoked) {
284
+ setApprovalRequired(true);
285
+ setBlobUrl(null);
286
+ void loadApprovalDetails();
287
+ }
288
+ return;
289
+ }
290
+ throw err;
291
+ }
292
+
293
+ if (revoked) return;
294
+ const token = tokenRes.token ?? null;
295
+ if (!tokenRes.success || !token) {
296
+ setApprovalRequired(true);
297
+ setBlobUrl(null);
298
+ void loadApprovalDetails();
299
+ return;
300
+ }
301
+ const themeStyles = collectCssVariables();
302
+ const fullHtml = buildAppHtml(html, themeStyles, appPath, token);
303
+ const blob = new Blob([fullHtml], { type: 'text/html' });
304
+ url = URL.createObjectURL(blob);
305
+ setBlobUrl(url);
306
+ } catch (err) {
307
+ if (!revoked) {
308
+ setBlobUrl(null);
309
+ setError(err instanceof Error ? err.message : 'Failed to load app');
310
+ }
311
+ } finally {
312
+ if (!revoked) setLoading(false);
313
+ }
314
+ })();
315
+
316
+ return () => {
317
+ revoked = true;
318
+ if (url) URL.revokeObjectURL(url);
319
+ };
320
+ }, [appPath, refreshKey, authToken, approvalRefreshKey]);
321
+
322
+ const handleApprove = useCallback(async () => {
323
+ if (!appPath) return;
324
+ setApprovalLoading(true);
325
+ setApprovalError(null);
326
+ try {
327
+ await api.post<{ success: boolean; error?: string }>(Api.Wallet, `/apps/${appPath}/approve`);
328
+ setApprovalRequired(false);
329
+ setApprovalRefreshKey((k) => k + 1);
330
+ } catch (err) {
331
+ setApprovalError(err instanceof Error ? err.message : 'Failed to approve app');
332
+ } finally {
333
+ setApprovalLoading(false);
334
+ }
335
+ }, [appPath]);
336
+
337
+ if (!appPath) {
338
+ return (
339
+ <div className="flex flex-col items-center justify-center h-full min-h-[100px] text-[var(--color-text-muted,#6b7280)]">
340
+ <Box size={24} className="mb-2" />
341
+ <span className="font-mono text-xs">NO APP CONFIGURED</span>
342
+ <span className="font-mono text-[9px] text-[var(--color-text-faint,#9ca3af)] mt-1">
343
+ Set config.appPath to load a app
344
+ </span>
345
+ </div>
346
+ );
347
+ }
348
+
349
+ if (loading) {
350
+ return (
351
+ <div className="flex flex-col items-center justify-center h-full min-h-[100px]">
352
+ <Loader2 size={20} className="animate-spin text-[var(--color-text-faint,#9ca3af)] mb-2" />
353
+ <span className="font-mono text-[9px] text-[var(--color-text-muted,#6b7280)]">LOADING APP...</span>
354
+ </div>
355
+ );
356
+ }
357
+
358
+ if (approvalRequired) {
359
+ return (
360
+ <div className="flex flex-col items-center justify-center h-full min-h-[100px] p-4">
361
+ <Box size={20} className="mb-2 text-[var(--color-text-muted,#6b7280)]" />
362
+ <span className="font-mono text-[9px] text-[var(--color-text,#0a0a0a)]">APP APPROVAL REQUIRED</span>
363
+ <span className="font-mono text-[8px] text-[var(--color-text-muted,#6b7280)] mt-1 text-center">
364
+ This app needs approval before it can run.
365
+ </span>
366
+ <div className="w-full max-w-[280px] mt-3 px-2 py-2 border border-[var(--color-border,#d4d4d8)] bg-[var(--color-background-alt,#f4f4f5)]">
367
+ <div className="font-mono text-[8px] text-[var(--color-text-muted,#6b7280)]">REQUESTED PERMISSIONS</div>
368
+ {approvalDetailsLoading ? (
369
+ <div className="flex items-center gap-1 mt-1">
370
+ <Loader2 size={10} className="animate-spin text-[var(--color-text-faint,#9ca3af)]" />
371
+ <span className="font-mono text-[8px] text-[var(--color-text-muted,#6b7280)]">Loading details...</span>
372
+ </div>
373
+ ) : approvalDetails?.permissions.length ? (
374
+ <div className="mt-1 flex flex-wrap gap-1">
375
+ {approvalDetails.permissions.map((permission) => (
376
+ <span
377
+ key={permission}
378
+ className="px-1.5 py-0.5 border border-[var(--color-border,#d4d4d8)] font-mono text-[8px] text-[var(--color-text,#0a0a0a)]"
379
+ >
380
+ {permission}
381
+ </span>
382
+ ))}
383
+ </div>
384
+ ) : (
385
+ <span className="font-mono text-[8px] text-[var(--color-text-muted,#6b7280)]">No explicit permissions listed.</span>
386
+ )}
387
+ </div>
388
+ <button
389
+ onClick={() => void handleApprove()}
390
+ disabled={approvalLoading}
391
+ className="mt-3 px-3 py-2 border border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface,#ffffff)] font-mono text-[9px] tracking-widest text-[var(--color-text,#0a0a0a)] hover:border-[var(--color-border-focus,#0a0a0a)] disabled:opacity-50 disabled:cursor-default"
392
+ >
393
+ {approvalLoading ? 'APPROVING...' : 'APPROVE'}
394
+ </button>
395
+ {approvalError && (
396
+ <span className="font-mono text-[8px] text-[var(--color-warning,#ff4d00)] mt-2 text-center">{approvalError}</span>
397
+ )}
398
+ </div>
399
+ );
400
+ }
401
+
402
+ if (error) {
403
+ return (
404
+ <div className="flex flex-col items-center justify-center h-full min-h-[100px] p-4">
405
+ <Box size={20} className="mb-2 text-[var(--color-warning,#ff4d00)]" />
406
+ <span className="font-mono text-[9px] text-[var(--color-warning,#ff4d00)]">FAILED TO LOAD APP</span>
407
+ <span className="font-mono text-[8px] text-[var(--color-text-muted,#6b7280)] mt-1 text-center">{error}</span>
408
+ </div>
409
+ );
410
+ }
411
+
412
+ if (!blobUrl) return null;
413
+
414
+ return (
415
+ <div className="relative h-full min-h-[100px]">
416
+ {/* SECURITY: sandbox without allow-same-origin. allow-popups-outside-sandbox
417
+ ensures links opened in new tabs are NOT sandboxed (so X.com etc. work). */}
418
+ <iframe
419
+ ref={iframeRef}
420
+ src={blobUrl}
421
+ sandbox="allow-scripts allow-popups allow-popups-outside-sandbox"
422
+ title={config?.appName || appPath}
423
+ className="w-full h-full border-0"
424
+ style={{ minHeight: '100px' }}
425
+ />
426
+ </div>
427
+ );
428
+ }