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.
- package/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- 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, '<').replace(/>/g, '>');
|
|
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
|
+
}
|