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,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy Source Fetcher
|
|
3
|
+
* =======================
|
|
4
|
+
* Fetches external data sources defined in strategy manifests.
|
|
5
|
+
* Handles dependency ordering, template variable substitution,
|
|
6
|
+
* JSONPath-lite extraction, and auth headers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SourceDef, StrategyManifest, StrategyConfig } from './types';
|
|
10
|
+
import { validateExternalUrl, sanitizePathSegment } from '../network';
|
|
11
|
+
import { getErrorMessage } from '../error';
|
|
12
|
+
|
|
13
|
+
const BASE_URL = 'http://127.0.0.1:4242';
|
|
14
|
+
|
|
15
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fetch all sources respecting dependency order.
|
|
19
|
+
* Independent sources (no `depends`) run in parallel,
|
|
20
|
+
* dependent sources run after their parent resolves.
|
|
21
|
+
*/
|
|
22
|
+
export async function fetchAllSources(
|
|
23
|
+
manifest: StrategyManifest,
|
|
24
|
+
config: StrategyConfig,
|
|
25
|
+
configOverrides?: Record<string, unknown> | null,
|
|
26
|
+
token?: string,
|
|
27
|
+
): Promise<Record<string, unknown[]>> {
|
|
28
|
+
const mergedConfig = { ...config, ...configOverrides };
|
|
29
|
+
const results: Record<string, unknown[]> = {};
|
|
30
|
+
|
|
31
|
+
// Separate independent and dependent sources
|
|
32
|
+
const independent: SourceDef[] = [];
|
|
33
|
+
const dependent: SourceDef[] = [];
|
|
34
|
+
|
|
35
|
+
for (const source of manifest.sources) {
|
|
36
|
+
if (source.depends) {
|
|
37
|
+
dependent.push(source);
|
|
38
|
+
} else {
|
|
39
|
+
independent.push(source);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fetch all independent sources in parallel
|
|
44
|
+
const independentResults = await Promise.all(
|
|
45
|
+
independent.map(async (source) => {
|
|
46
|
+
try {
|
|
47
|
+
const data = await fetchSource(source, mergedConfig, manifest.id, undefined, token, manifest.allowedHosts);
|
|
48
|
+
return { id: source.id, data };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (source.optional) return { id: source.id, data: [] };
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
for (const { id, data } of independentResults) {
|
|
57
|
+
results[id] = data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Fetch dependent sources in order (sources are already topologically sorted by loader)
|
|
61
|
+
for (const source of dependent) {
|
|
62
|
+
const parentData = source.depends ? results[source.depends] : undefined;
|
|
63
|
+
try {
|
|
64
|
+
const data = await fetchSource(source, mergedConfig, manifest.id, parentData, token, manifest.allowedHosts);
|
|
65
|
+
results[source.id] = data;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (source.optional) {
|
|
68
|
+
results[source.id] = [];
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Fetch a single source with timeout */
|
|
79
|
+
async function fetchSource(
|
|
80
|
+
source: SourceDef,
|
|
81
|
+
config: StrategyConfig,
|
|
82
|
+
appId: string,
|
|
83
|
+
parentData?: unknown[],
|
|
84
|
+
token?: string,
|
|
85
|
+
allowedHosts?: string[],
|
|
86
|
+
): Promise<unknown[]> {
|
|
87
|
+
const rawUrl = resolveUrl(source.url, config, parentData);
|
|
88
|
+
const isInternal = rawUrl.startsWith('/');
|
|
89
|
+
let url = isInternal ? BASE_URL + rawUrl : rawUrl;
|
|
90
|
+
|
|
91
|
+
// SSRF protection: validate external URLs against private IPs and allowedHosts
|
|
92
|
+
if (!isInternal) {
|
|
93
|
+
await validateExternalUrl(url, allowedHosts);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const headers = await getAuthHeaders(source, appId, token);
|
|
97
|
+
// Internal endpoints get the strategy token automatically
|
|
98
|
+
if (isInternal && token && !headers['Authorization']) {
|
|
99
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
100
|
+
}
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
103
|
+
const hasAuth = Object.keys(headers).length > 0;
|
|
104
|
+
|
|
105
|
+
console.log(`[strategy:${appId}] fetch ${source.id}: ${source.method} ${url}${hasAuth ? ' (auth)' : ''}`);
|
|
106
|
+
const fetchStart = Date.now();
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const options: RequestInit = {
|
|
110
|
+
method: source.method,
|
|
111
|
+
headers,
|
|
112
|
+
signal: controller.signal,
|
|
113
|
+
...(!isInternal ? { redirect: 'error' as const } : {}),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (source.method === 'POST' && source.body) {
|
|
117
|
+
(options.headers as Record<string, string>)['Content-Type'] = 'application/json';
|
|
118
|
+
options.body = JSON.stringify(source.body);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const response = await fetch(url, options);
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw new Error(`Source "${source.id}" returned ${response.status}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
const fetchMs = Date.now() - fetchStart;
|
|
128
|
+
|
|
129
|
+
let result: unknown[];
|
|
130
|
+
if (source.select) {
|
|
131
|
+
result = applySelect(data, source.select);
|
|
132
|
+
} else {
|
|
133
|
+
result = Array.isArray(data) ? data : [data];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(`[strategy:${appId}] fetch ${source.id}: ${fetchMs}ms → ${result.length} item(s)`);
|
|
137
|
+
return result;
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const fetchMs = Date.now() - fetchStart;
|
|
140
|
+
const errMsg = getErrorMessage(err);
|
|
141
|
+
console.error(`[strategy:${appId}] fetch ${source.id}: FAILED in ${fetchMs}ms — ${errMsg}`);
|
|
142
|
+
throw err;
|
|
143
|
+
} finally {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Template variable substitution on URL.
|
|
150
|
+
* Replaces ${config.x} with config values.
|
|
151
|
+
* Replaces ${key} with config values or comma-joined parent data values.
|
|
152
|
+
*/
|
|
153
|
+
export function resolveUrl(
|
|
154
|
+
url: string,
|
|
155
|
+
config: StrategyConfig,
|
|
156
|
+
parentData?: unknown[],
|
|
157
|
+
): string {
|
|
158
|
+
return url.replace(/\$\{([^}]+)\}/g, (_, expr: string) => {
|
|
159
|
+
// ${config.x} — explicit config reference
|
|
160
|
+
if (expr.startsWith('config.')) {
|
|
161
|
+
const key = expr.slice('config.'.length);
|
|
162
|
+
const val = config[key];
|
|
163
|
+
return val != null ? String(val) : '';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Try config first
|
|
167
|
+
if (config[expr] != null) {
|
|
168
|
+
return String(config[expr]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Try extracting values from parent data
|
|
172
|
+
if (parentData && parentData.length > 0) {
|
|
173
|
+
const values = parentData
|
|
174
|
+
.map((item) => {
|
|
175
|
+
if (item != null && typeof item === 'object') {
|
|
176
|
+
return (item as Record<string, unknown>)[expr];
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
})
|
|
180
|
+
.filter((v) => v != null);
|
|
181
|
+
|
|
182
|
+
if (values.length > 0) {
|
|
183
|
+
return values.join(',');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return '';
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* JSONPath-lite extraction.
|
|
193
|
+
* `select.items` defines where the array is (e.g., `$.data`).
|
|
194
|
+
* Other keys extract fields from each item.
|
|
195
|
+
*/
|
|
196
|
+
export function applySelect(
|
|
197
|
+
data: unknown,
|
|
198
|
+
select: Record<string, string>,
|
|
199
|
+
): unknown[] {
|
|
200
|
+
const itemsPath = select.items;
|
|
201
|
+
const items = itemsPath ? resolvePath(data, itemsPath) : data;
|
|
202
|
+
|
|
203
|
+
if (!Array.isArray(items)) {
|
|
204
|
+
return items != null ? [items] : [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// If there are no field selectors beyond `items`, return raw items
|
|
208
|
+
const fieldKeys = Object.keys(select).filter((k) => k !== 'items');
|
|
209
|
+
if (fieldKeys.length === 0) return items;
|
|
210
|
+
|
|
211
|
+
// Extract specified fields from each item
|
|
212
|
+
return items.map((item) => {
|
|
213
|
+
const result: Record<string, unknown> = {};
|
|
214
|
+
for (const key of fieldKeys) {
|
|
215
|
+
result[key] = resolvePath(item, select[key]);
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Simple path navigation: $.field.nested or $.array.0.field
|
|
223
|
+
* `$` alone returns root.
|
|
224
|
+
*/
|
|
225
|
+
export function resolvePath(obj: unknown, path: string): unknown {
|
|
226
|
+
if (path === '$') return obj;
|
|
227
|
+
|
|
228
|
+
const parts = path.replace(/^\$\.?/, '').split('.');
|
|
229
|
+
let current: unknown = obj;
|
|
230
|
+
|
|
231
|
+
for (const part of parts) {
|
|
232
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
233
|
+
|
|
234
|
+
// Numeric index for arrays
|
|
235
|
+
if (Array.isArray(current) && /^\d+$/.test(part)) {
|
|
236
|
+
current = current[parseInt(part, 10)];
|
|
237
|
+
} else {
|
|
238
|
+
current = (current as Record<string, unknown>)[part];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return current;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get auth headers for a source.
|
|
247
|
+
* Reads API key from AppStorage by source.key.
|
|
248
|
+
* Returns headers based on source.auth type.
|
|
249
|
+
*/
|
|
250
|
+
export async function getAuthHeaders(
|
|
251
|
+
source: SourceDef,
|
|
252
|
+
appId: string,
|
|
253
|
+
token?: string,
|
|
254
|
+
): Promise<Record<string, string>> {
|
|
255
|
+
const headers: Record<string, string> = {};
|
|
256
|
+
|
|
257
|
+
if (!source.auth || source.auth === 'none' || !source.key) {
|
|
258
|
+
return headers;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let apiKey = '';
|
|
262
|
+
|
|
263
|
+
if (token) {
|
|
264
|
+
try {
|
|
265
|
+
const safeAppId = sanitizePathSegment(appId);
|
|
266
|
+
const res = await fetch(`${BASE_URL}/apps/${safeAppId}/apikey/${source.key}`, {
|
|
267
|
+
method: 'GET',
|
|
268
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
269
|
+
});
|
|
270
|
+
if (res.ok) {
|
|
271
|
+
const body = await res.json();
|
|
272
|
+
apiKey = body.value ?? '';
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.warn(`[strategy:${appId}] failed to fetch API key for source ${source.key}:`, getErrorMessage(err));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (source.auth === 'bearer') {
|
|
280
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
281
|
+
} else if (source.auth === 'header') {
|
|
282
|
+
const headerName = source.header || 'X-API-Key';
|
|
283
|
+
headers[headerName] = apiKey;
|
|
284
|
+
}
|
|
285
|
+
// 'query' auth is handled via URL params, not headers
|
|
286
|
+
|
|
287
|
+
return headers;
|
|
288
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy State Manager
|
|
3
|
+
* ======================
|
|
4
|
+
* In-memory state with persistence via REST API (AppStorage endpoints).
|
|
5
|
+
* State is kept in memory for fast access, persisted on demand through
|
|
6
|
+
* authenticated REST calls using the strategy's token.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { sanitizePathSegment } from '../network';
|
|
10
|
+
|
|
11
|
+
const STATE_KEY = '_strategy_state';
|
|
12
|
+
const CONFIG_KEY = '_strategy_config';
|
|
13
|
+
const BASE_URL = 'http://127.0.0.1:4242';
|
|
14
|
+
|
|
15
|
+
/** In-memory state for all active strategies */
|
|
16
|
+
const states = new Map<string, Record<string, unknown>>();
|
|
17
|
+
|
|
18
|
+
/** Per-strategy auth tokens for REST API calls */
|
|
19
|
+
const tokens = new Map<string, string>();
|
|
20
|
+
|
|
21
|
+
/** Set the auth token for a strategy */
|
|
22
|
+
export function setToken(id: string, token: string): void {
|
|
23
|
+
tokens.set(id, token);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Clear the auth token for a strategy */
|
|
27
|
+
export function clearToken(id: string): void {
|
|
28
|
+
tokens.delete(id);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Get state for a strategy (creates empty object if none) */
|
|
32
|
+
export function getState(id: string): Record<string, unknown> {
|
|
33
|
+
let state = states.get(id);
|
|
34
|
+
if (!state) {
|
|
35
|
+
state = {};
|
|
36
|
+
states.set(id, state);
|
|
37
|
+
}
|
|
38
|
+
return state;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Merge updates into existing state */
|
|
42
|
+
export function updateState(id: string, updates: Record<string, unknown>): void {
|
|
43
|
+
const state = getState(id);
|
|
44
|
+
Object.assign(state, updates);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Persist state to DB via REST API */
|
|
48
|
+
export async function persistState(strategyId: string): Promise<void> {
|
|
49
|
+
const state = states.get(strategyId);
|
|
50
|
+
if (!state) return;
|
|
51
|
+
|
|
52
|
+
const token = tokens.get(strategyId);
|
|
53
|
+
if (!token) {
|
|
54
|
+
console.error(`[strategy:${strategyId}] persistState: no token set, skipping`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const safeId = sanitizePathSegment(strategyId);
|
|
59
|
+
const res = await fetch(`${BASE_URL}/apps/${safeId}/storage/${STATE_KEY}`, {
|
|
60
|
+
method: 'PUT',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
'Authorization': `Bearer ${token}`,
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({ value: state }),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const text = await res.text().catch(() => '');
|
|
70
|
+
throw new Error(`persistState failed (${res.status}): ${text}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Restore state from DB via REST API into memory */
|
|
75
|
+
export async function restoreState(strategyId: string): Promise<void> {
|
|
76
|
+
const token = tokens.get(strategyId);
|
|
77
|
+
if (!token) {
|
|
78
|
+
console.error(`[strategy:${strategyId}] restoreState: no token set, skipping`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const safeId = sanitizePathSegment(strategyId);
|
|
83
|
+
const res = await fetch(`${BASE_URL}/apps/${safeId}/storage/${STATE_KEY}`, {
|
|
84
|
+
method: 'GET',
|
|
85
|
+
headers: {
|
|
86
|
+
'Authorization': `Bearer ${token}`,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (res.status === 404) {
|
|
91
|
+
// No stored state — initialize empty
|
|
92
|
+
if (!states.has(strategyId)) {
|
|
93
|
+
states.set(strategyId, {});
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
const text = await res.text().catch(() => '');
|
|
100
|
+
throw new Error(`restoreState failed (${res.status}): ${text}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const body = await res.json();
|
|
104
|
+
const value = body.value;
|
|
105
|
+
|
|
106
|
+
if (value != null && typeof value === 'object' && !Array.isArray(value)) {
|
|
107
|
+
states.set(strategyId, value as Record<string, unknown>);
|
|
108
|
+
} else if (typeof value === 'string') {
|
|
109
|
+
// Handle double-stringified values (app SDK may pre-stringify)
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(value);
|
|
112
|
+
states.set(strategyId, typeof parsed === 'object' && parsed !== null ? parsed : {});
|
|
113
|
+
} catch {
|
|
114
|
+
states.set(strategyId, {});
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
states.set(strategyId, {});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Read config overrides via REST API */
|
|
122
|
+
export async function getConfigOverrides(
|
|
123
|
+
strategyId: string,
|
|
124
|
+
): Promise<Record<string, unknown> | null> {
|
|
125
|
+
const token = tokens.get(strategyId);
|
|
126
|
+
if (!token) {
|
|
127
|
+
console.error(`[strategy:${strategyId}] getConfigOverrides: no token set, skipping`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const safeId = sanitizePathSegment(strategyId);
|
|
132
|
+
const res = await fetch(`${BASE_URL}/apps/${safeId}/storage/${CONFIG_KEY}`, {
|
|
133
|
+
method: 'GET',
|
|
134
|
+
headers: {
|
|
135
|
+
'Authorization': `Bearer ${token}`,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (res.status === 404) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
const text = await res.text().catch(() => '');
|
|
145
|
+
throw new Error(`getConfigOverrides failed (${res.status}): ${text}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const body = await res.json();
|
|
149
|
+
const value = body.value;
|
|
150
|
+
|
|
151
|
+
if (value != null && typeof value === 'object' && !Array.isArray(value)) {
|
|
152
|
+
return value as Record<string, unknown>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Write config overrides via REST API */
|
|
159
|
+
export async function setConfigOverrides(
|
|
160
|
+
strategyId: string,
|
|
161
|
+
overrides: Record<string, unknown>,
|
|
162
|
+
): Promise<void> {
|
|
163
|
+
const token = tokens.get(strategyId);
|
|
164
|
+
if (!token) {
|
|
165
|
+
console.error(`[strategy:${strategyId}] setConfigOverrides: no token set, skipping`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const safeId = sanitizePathSegment(strategyId);
|
|
170
|
+
const res = await fetch(`${BASE_URL}/apps/${safeId}/storage/${CONFIG_KEY}`, {
|
|
171
|
+
method: 'PUT',
|
|
172
|
+
headers: {
|
|
173
|
+
'Content-Type': 'application/json',
|
|
174
|
+
'Authorization': `Bearer ${token}`,
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify({ value: overrides }),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!res.ok) {
|
|
180
|
+
const text = await res.text().catch(() => '');
|
|
181
|
+
throw new Error(`setConfigOverrides failed (${res.status}): ${text}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Persist all states (for shutdown) */
|
|
186
|
+
export async function persistAllStates(): Promise<void> {
|
|
187
|
+
const ids = Array.from(states.keys());
|
|
188
|
+
await Promise.all(ids.map((id) => persistState(id)));
|
|
189
|
+
}
|