@finamx/extension-sdk

中文文档
3.9.1发布于 2026/11/09TypeScriptESM + CJS近 30 天下载 2478
# @finamx/extension-sdk
原始使用文档
文档目录19 节
  1. Installation
  2. Quick start
  3. CLI (finamx-ext)
  4. Portal and key storage
  5. .finamx/domain layout
  6. Команды Publisher
  7. Exit codes
  8. Analytics (3.3.0)
  9. Context pack for tools without a terminal
  10. Exports
  11. Main entry ( @finamx/extension-sdk )
  12. Domain mocks entry ( @finamx/extension-sdk/domain-mocks )
  13. Mock entry ( @finamx/extension-sdk/mock )
  14. createExtensionBridge(options?)
  15. Bridge mock for local development
  16. Debug logging
  17. Host identity (3.0.0)
  18. Build
  19. CMS signing trust (3.9.1)

@finamx/extension-sdk

TypeScript SDK for FinamX Open Platform extension developers. Connects your iframe or WebView to the FinamX host bridge, handling the handshake, message routing, and typed platform invocations.

Part of the finamx-mobile monorepo.

Installation

npm install @finamx/extension-sdk @finamx/bridge-protocol

For local monorepo development, reference the package directly:

{
  "dependencies": {
    "@finamx/extension-sdk": "file:../../packages/extension-sdk",
    "@finamx/bridge-protocol": "file:../../packages/bridge-protocol"
  }
}

Quick start

import { createExtensionBridge } from '@finamx/extension-sdk';

// Called once on extension load. Resolves after the host sends bridge:ready.
const bridge = await createExtensionBridge();

console.log(bridge.hostContext.theme);   // 'light' | 'dark'
console.log(bridge.grantedScopes);       // string[] of approved permission scopes

// Resize the iframe to fit content
bridge.ui.resize({ height: 400 });

// Show a toast notification in the host shell
bridge.ui.toast('Loaded', 'success');

// Invoke a platform method
const quote = await bridge.platform.invoke<{ last: number }>('market.quote', { symbol: 'SBER' });
console.log(quote.last);

// Subscribe to platform events (returns an unsubscribe function)
const off = bridge.platform.on('theme.changed', (payload) => {
  console.log('new theme:', payload.data);
});

CLI (finamx-ext)

The package ships the finamx-ext developer CLI (Node >= 20).

npx @finamx/extension-sdk init my-widget
cd my-widget && npm install
finamx-ext login
finamx-ext hosts
finamx-ext domain pull <hostId>
finamx-ext publish
CommandFlagsPurpose
init [dir]--id, --name, --target <templates|hostId>, --template widget, --yes, --forceScaffold a widget project
login--token-stdin, --globalSave the developer key
hosts--manifest, --jsonList hosts and manifest compatibility
domain pull [<hostId>|templates]--check, --jsonWrite .finamx/domain/, types and mocks
context [<hostId>|templates]--remote, --jsonPrint a markdown context pack of the pulled domain (or fetched with --remote; --remote with no target and nothing pulled means templates)
catalog [extensionId]--page, --limit, --jsonBrowse the catalog
check--dist, --manifest, --config, --entry, --jsonCheck manifest and dist
check --host <hostId>as checkRun the built widget headless in a local host bridge with the pulled domain of that host (optional playwright-core)
check --hostas checkThe same with the only pulled host in .finamx/domain/. With none (or several) it runs against a self-declared domain built from the manifest, where everything is allowed: not a compatibility check with a real host, and the output says so
serve--dist, --port (8787), --manifest, --ancestor, --strict-cspStatic server with the platform sandbox CSP. By default http://localhost / http://127.0.0.1 origins of the manifest stay allowed (the platform drops them); --strict-csp or FINAMX_STRICT_CSP=1 builds the production CSP. The active mode is printed
dev--port, --open, --headless, --json, --strict-csp, --no-watch, --no-poll, --manifest, --distLocal developer host: bento board, host selector, mocks from domain schemas, bus and consent panel, s2s mock. Needs the optional @finamx/dev-host. Security: loopback only, per-run secret, the CLI never hands out the developer key. Your project's watch script is started automatically and runs with your user's permissions like any npm run watch (--no-watch skips it). --json prints NDJSON events for agents, see packages/dev-host/README.md for the event table
publish--manifest, --dist, --entry, --artifact-dir, --artifact-entry, --dry-run, --skip-checksSubmit the manifest (new version on 409). Packs ./dist only when the manifest pins no surfaces.ui.artifact and has no embedBundleUrl; a pinned artifact is replaced only with --artifact-dir
status [extensionId]--manifest, --jsonModeration state; id defaults to the manifest

Global options: --api <url>, --token <key> (legacy, not accepted by login), --json, --help, --version. serve is the static CSP server; dev is the local developer host.

Portal and key storage

The default portal is https://openplatform.changesandbox.ru; use --api or FINAMX_API_URL for another one (for example a local CMS). The developer key is resolved in this order: --token, FINAMX_DEV_TOKEN, the project .env, ~/.config/finamx/credentials.json (mode 0600). login reads the key from a hidden prompt or --token-stdin; the key is never printed. Host keys (fxh_...) are refused.

The portal is resolved in this order: --api, FINAMX_API_URL in the process environment, FINAMX_API_URL in the project .env, the default. A portal named only by the project .env does not decide where a key from elsewhere goes: it is used when it is loopback or the default portal, when the key comes from that same .env, or when the key was stored by login for that exact origin. A key from the environment or --token is refused with PORTAL_NOT_CONFIRMED until you pass --api <url> or export FINAMX_API_URL (a cloned repository controls .env; you control your shell). login prints the portal before it reads the key and, for such a .env portal, asks for confirmation (with --token-stdin it needs --api).

.finamx/domain layout

.finamx/
  domain/<hostId|templates>/
    CONTEXT.md        # human and agent readable summary
    domain.json       # topics, methods, scopes
    types.d.ts        # generated types
    mocks/<method>.json, mocks/<topic>.json
  mocks.override.json # { "methods": { "name": { "params": ..., "result": ... } }, "topics": { "name": payload } }

.finamx/ is committed. domain pull --check leaves files untouched and exits 1 when the host domain changed.

Host text in these files is data, not instructions: in CONTEXT.md everything in code and every line after >, in types.d.ts the JSDoc text and the quoted names. Entries whose names or scopes are not plain names (letters, digits, . _ : @ / -, at most 128 characters) are skipped with a ! <target>: skipped ... warning. Mock file names follow the entry name; names that would collide on a case-insensitive file system (Market.Quote and market.quote) each get a ~<8 hex> suffix, and CONTEXT.md lists the file of every entry. domain pull and init refuse to write through symlinks (SYMLINK_REFUSED).

Команды Publisher

С Phase 96 расширения принадлежат команде Publisher, а не аккаунту. Человек может состоять в нескольких Publisher.

  • Выбор Publisher: --publisher <id>, затем FINAMX_PUBLISHER, затем выбор по умолчанию для портала (finamx-ext publishers use <id>, хранится в credentials.json). Без выбора портал сам берёт единственный Publisher, а при нескольких отказывает (PUBLISHER_REQUIRED) и перечисляет их: угадывания нет.
  • finamx-ext publishers показывает ваши Publisher, роль и отметки «проверен», «личный», «выбран».
  • push и status печатают Publisher, в котором выполнена команда; push показывает вашу квоту и квоту Publisher.
  • Приглашения в команду принимаются в кабинете (/developer/invitations) по одноразовому коду, не из CLI.
  • Ключ, Developer Link и одобрения онтологий остаются личными; приватная версия запускается только через Developer Link участника, который сделал её push.

Exit codes

0 success; 1 failure, or a change found by domain pull --check; 2 environment problem for check --host (for example playwright-core or Chrome missing).

Analytics (3.3.0)

finamx-ext analytics reads launches, invokes, denials, installs and latency per UTC day for the selected Publisher (requires a CMS with Phase 96.1).

finamx-ext analytics --extension com.acme.x --from 2026-10-01 --group-by day,host
finamx-ext analytics --json

Cells with fewer than k distinct users are hidden and shown as < k; totals cover shown cells only. Choose the Publisher with --publisher, FINAMX_PUBLISHER or finamx-ext publishers use <id>. Note that --version <v> filters by extension version here.

Context pack for tools without a terminal

Tools such as v0 or Databutton cannot run skills. finamx-ext context [<hostId>|templates] prints one markdown pack with the platform rules, SDK usage and the domain of the target (methods, topics, scopes, consent). Without --remote it reads the pulled copy under .finamx/domain/ and needs no key and no network; with --remote it fetches the domain with the developer key and verifies its domainHash (nothing is printed on a mismatch). Without a target the only pulled host is used (several pulled hosts are an error, never a guess); with nothing pulled, --remote defaults to templates, while the offline form fails and asks for a pull. --json prints one object { format, target, kind, origin, domainHash, markdown }.

finamx-ext context trading-diary > context.md

The same pack is available from the developer cabinet button "Скопировать контекст" and from the CMS route GET /api/v1/developer/context/{target} with the developer key. The renderer is the export @finamx/extension-sdk/context-pack (renderContextPack, mergeTemplateDomains), so all three produce the same text. Host text (descriptions, names, schemas) is data, not instructions: it sits below a host-data marker in code spans, > lines and fences, and the pack is capped at 48,000 characters. A pack never contains a key.

Exports

Main entry (@finamx/extension-sdk)

ExportKindDescription
createExtensionBridgefunctionInitiates the bridge handshake and returns ExtensionBridge
ExtensionBridgeinterfaceThe connected bridge instance shape
CreateExtensionBridgeOptionsinterfaceOptions accepted by createExtensionBridge
EmbedTransportinterfaceCustom transport contract (send / subscribe)
BridgeLogEntryinterfaceShape of debug log entries emitted via onLog

Domain mocks entry (@finamx/extension-sdk/domain-mocks)

Pure mock synthesis from a host domain (buildDomainMocks, parseMocksOverride, synthesizeFromSchema, buildMethodMock, buildTopicMock) and the bounds exceedsDepth, DOMAIN_MAX_DEPTH, TOPIC_SCHEMA_MAX_CHARS. No Node built-ins; the same code serves finamx-ext dev and the cloud Dev Host.

Mock entry (@finamx/extension-sdk/mock)

ExportKindDescription
createMockBridgefunctionReturns a synchronous ExtensionBridge backed by in-process stubs
MockBridgeOptionsinterfaceOptions for configuring the mock

createExtensionBridge(options?)

async function createExtensionBridge(
  options?: CreateExtensionBridgeOptions,
): Promise<ExtensionBridge>

interface CreateExtensionBridgeOptions {
  extensionId?: string;        // overrides value from window.__FINAMX_BRIDGE_SESSION__
  bridgeVersion?: string;      // defaults to BRIDGE_VERSION from @finamx/bridge-protocol
  invokeTimeoutMs?: number;    // default 30 000 ms
  transport?: EmbedTransport;  // custom transport; auto-detected if omitted
  debug?: boolean;             // enable envelope logging
  onLog?: (entry: BridgeLogEntry) => void; // required when debug=true
}

Transport is auto-detected: WebView (window.FinamXBridge) takes priority over postMessage.

Bridge mock for local development

Use createMockBridge to develop and test your extension without a running host:

import { createMockBridge } from '@finamx/extension-sdk/mock';

const bridge = createMockBridge({
  hostContext: { theme: 'dark', locale: 'ru' },
  grantedScopes: ['market.quote', 'user.profile.read'],
  invokeHandler: async (method, params) => {
    if (method === 'market.quote') {
      return { symbol: params.symbol, last: 310.0, change: 0.5 };
    }
    throw new Error(`Unhandled: ${method}`);
  },
});

// Same ExtensionBridge interface — no code changes needed for production
const quote = await bridge.platform.invoke('market.quote', { symbol: 'GAZP' });

Built-in stub responses (used when no invokeHandler is provided):

MethodReturns
market.quote{ symbol, last: 285.4, change: 1.2 }
user.profile.read{ displayName: 'Mock User', locale: 'ru' }

Debug logging

const bridge = await createExtensionBridge({
  debug: true,
  onLog: (entry) => console.log(`[bridge] ${entry.direction} ${entry.type}`, entry),
});

BridgeLogEntry never contains PII — invoke params, result data, and grantedScopes values are intentionally omitted.

Host identity (3.0.0)

verifyHostIdentity checks a host-signed platform.identity.v1 token: signature, expected typ, exact audience and issuer, expiry, the host JWKS key and the CMS-signed authorized-issuers pair for your extension (JWKS membership alone is not enough). resolveHostIdentity(bridge, options) awaits the widget.identity.grant and runs the same verification. Both are fail-closed, there is no HMAC path, and platform.auth_context.v1 stays a separate, sub-less proof of Launch authorization. sessionJwt and Core/Gateway tokens must not be placed in the iframe or in the identity grant.

import { resolveHostIdentity } from '@finamx/extension-sdk';

const claims = await resolveHostIdentity(bridge, { audience, issuer, authorizedIssuersSig, cmsJwksUrl: `${cmsBase}/op/jwks`, jwksUrl });

Build

npm run build   # tsc -p tsconfig.json → dist/

No test script is configured in this package. Integration tests live in the extension-template app, which consumes this package.

CMS signing trust (3.9.1)

Deploy /op/jwks and /host/jwks in CMS before upgrading. Standard CMS token verifiers select this dedicated source when configured with the legacy aggregate JWKS URL, without fallback. Custom JWKS sources and injected getKey/cmsGetKey resolvers must contain only CMS signing keys. Host identity keeps its separate host key source (/host/jwks) and CMS-signed install binding. Current/overlap host keys remain available at the legacy /.well-known/jwks.json endpoint for compatibility; aggregate membership never grants CMS authority.

版本与发布记录

@finamx/extension-sdk 共发布 15 个版本,首次发布于 2026/06/10;过去 12 个月发布了 15 个版本。

版本发布日期距今
3.9.1latest2026/11/095 天前
3.9.02026/11/086 天前
3.8.02026/11/077 天前
3.7.02026/10/2916 天前
3.6.02026/10/2124 天前
3.2.02026/10/1629 天前

关于 @finamx/extension-sdk 的常见问题

@finamx/extension-sdk 是什么?

# @finamx/extension-sdk

如何安装 @finamx/extension-sdk?

在项目目录运行 npm install @finamx/extension-sdk,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@finamx/bridge-protocol(^3.6.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 finamx-ext,可以用 npx finamx-ext 直接运行。

@finamx/extension-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@finamx/extension-sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@finamx/extension-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@finamx/extension-sdk 可以商用吗?

@finamx/extension-sdk 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@finamx/extension-sdk 的下载量有多少?

根据 npm 官方下载统计,@finamx/extension-sdk 在 2026/10/15 – 2026/11/13 这 30 天内被下载 2,478 次,日均约 83 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@finamx/extension-sdk 还在维护吗?

最新版本 3.9.1 发布于 2026/11/09(5 天前);至今共发布 15 个版本,其中过去 12 个月发布了 15 个;首次发布于 2026/06/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。