@jitto/analyze-embed

中文文档
0.4.0发布于 2026/11/01SEE LICENSE IN LICENSETypeScriptES Module零依赖

Embed Jitto dashboards in your web app, in React or plain JavaScript.

原始使用文档
文档目录13 节
  1. Install
  2. Quickstart
  3. React: <JittoDashboard>
  4. Plain JavaScript: mountJittoDashboard
  5. Tokens
  6. Events
  7. Size
  8. Filters
  9. Theme
  10. Errors and troubleshooting
  11. Supported browsers and frameworks
  12. Versioning
  13. Full guide and API reference

@jitto/analyze-embed

Guide: GUIDE.md, shipped in this package, takes you from nothing to an embedded dashboard.

Show a Jitto dashboard inside your own web app, for your own signed-in users, in React or plain JavaScript. Each user sees only the rows Jitto's row rules allow them; your page never holds a Jitto password or secret.

Status: 0.4. Expect small changes before 1.0. What changed in each version: CHANGELOG.md.

Not available yet: filterChanged fires for the filters your app sets (source: 'host') only; changes the user makes in the filter bar are not reported yet. Everything else in this README works as described.

Filters need your Jitto server to list them. Pass filters (or call setFilters) only once your Jitto server returns filter_bar in its token answer. Until then every filter is refused: a mount with filters ends with failed and the dashboard does not load. In 0.2.0 the filters option was ignored instead.

Install

npm install @jitto/analyze-embed

One package, two entry points: @jitto/analyze-embed (plain JavaScript) and @jitto/analyze-embed/react (React). Both run on the same core, so the same options give the same events. TypeScript declarations are included.

Quickstart

  1. Get three things from your Jitto tenant administrator: your tenant's Jitto host (for example https://example-tenant.jittocloud.com; -uat and -dev hosts for the other environments), the id of the dashboard, and confirmation that your website's origin (for example https://portal.client.example) is on the tenant's allowed origins. Origins are exact: https://, host, optional port.
  2. Install: npm install @jitto/analyze-embed.
  3. Write fetchToken (see Tokens): it asks Jitto for a dashboard token with the signed-in user's own access token from your sign-in library.
  4. Mount the dashboard:
import { JittoDashboard } from '@jitto/analyze-embed/react';

export function DeskOverview() {
  return (
    <JittoDashboard
      dashboardId="3f0c6a52-8d1e-4b7a-9c2f-5e8d71a4b6c0"
      embedOrigin="https://example-tenant.jittocloud.com"
      fetchToken={fetchToken} // see Tokens: it sends the user's own access token
      style={{ height: 600 }}
    />
  );
}

Open the page as a test user. You see that user's rows.

React: <JittoDashboard>

import { useRef } from 'react';
import { JittoDashboard } from '@jitto/analyze-embed/react';
import type { JittoDashboardHandle } from '@jitto/analyze-embed';

export function Report() {
  const dashboard = useRef<JittoDashboardHandle>(null);
  return (
    <JittoDashboard
      ref={dashboard}
      dashboardId="3f0c6a52-8d1e-4b7a-9c2f-5e8d71a4b6c0"
      embedOrigin="https://example-tenant.jittocloud.com"
      fetchToken={fetchToken}
      theme={{ mode: 'light', colorPrimary: '#0b7a75' }}
      onLoaded={({ durationMs }) => console.log(`loaded in ${durationMs} ms`)}
      onFailed={({ reason, message }) => console.warn(reason, message)}
      onSizeChanged={({ height }) => console.log('frame height', height)}
      className="report"
    />
  );
}
PropTypeRequiredWhat it does
dashboardIdstringyesThe Jitto dashboard id your tenant administrator gave you.
embedOriginstringyesYour tenant's Jitto host, as an origin (https://example-tenant.jittocloud.com). The SDK frames only this origin followed by /embed (the API's embed_url) and talks only to this origin. It derives no host from anything else.
fetchToken(dashboardId, signal) => Promise<EmbedTokenResponse>yesGets a dashboard token for the signed-in user with their own access token. See Tokens.
filtersFiltersnoFilters applied before the first render, by filter name. A refused one ends the mount with failed. See Filters.
themeThemeTokensnoColours, font, light or dark. See Theme.
tokenDeadlineMsnumbernoDeadline for one token request. Default 20000.
loadDeadlineMsnumbernoHow long the frame may stay silent before failed fires with reason network. Default 30000. A mount that is not displayed, as in a hidden tab, waits until it is shown.
retryDelaysMsreadonly number[]noWaits before each retry of a retryable token failure (429, 500, 502, deadline, network). Default [2000, 5000, 15000]. On mount, a Retry-After over 60 seconds is not waited for: failed fires instead.
fetchDownload(request, signal) => Promise<Response>noDeprecated and ignored: download() asks the dashboard, which downloads on its own session. Removed in the next minor release.
downloadDeadlineMsnumbernoDeadline for one download(), including the dashboard's wait for a PDF. Default 180000.
onLoaded(e) => voidnoThe dashboard rendered. See Events.
onFailed(e) => voidnoThe dashboard could not load, or stopped working.
onFilterChanged(e) => voidnoThe filters your app set were applied (source: 'host').
onFilterRejected(e) => voidnoThe dashboard refused filters from setFilters or a changed filters prop. Nothing was applied. For a prop change this is the only report.
onSizeChanged(e) => voidnoThe dashboard frame's size on the page changed. See Size.
classNamestringnoClass of the container element.
styleRecord<string, string | number>noStyle of the container element. Give it a height.
refRef<JittoDashboardHandle>noThe same handle mountJittoDashboard returns.

How prop changes apply:

  • A change to dashboardId or embedOrigin remounts the dashboard.
  • theme is compared by value. An equal value does nothing, so an inline object is safe. A different value is applied in place, as setTheme would. Removing theme changes nothing: a theme can be replaced, not removed, so the dashboard keeps its current look.
  • filters is compared by value too, key order ignored, so filters={{ Ccy: userCcy }} is safe inline. A different value is applied as setFilters would; a refusal calls onFilterRejected and the filters in effect stay. Removing filters changes nothing; pass {} to clear every filter your app set. Use either the prop or the handle's setFilters for filters, not both.
  • Callbacks, the token function and the deadlines are read when used. A new function takes effect from its next use, with no remount.

The component mounts in the browser only. In a server-rendered app, render it on the client.

Plain JavaScript: mountJittoDashboard

import { mountJittoDashboard } from '@jitto/analyze-embed';

const element = document.getElementById('dashboard');
if (!element) throw new Error('no #dashboard element on the page');

const dashboard = mountJittoDashboard(element, {
  dashboardId: '3f0c6a52-8d1e-4b7a-9c2f-5e8d71a4b6c0',
  embedOrigin: 'https://example-tenant.jittocloud.com',
  fetchToken,
  onFailed: ({ reason, message }) => console.warn(reason, message),
});

const stop = dashboard.on('loaded', ({ durationMs }) => console.log(`loaded in ${durationMs} ms`));

// Later, when your app's own controls change:
export function useDarkTheme() {
  dashboard.setTheme({ mode: 'dark' });
}

// When your page or view goes away:
export function teardown() {
  stop();
  dashboard.unmount();
}

mountJittoDashboard(element, options) takes the same options and callbacks as the React props (except className, style and ref) and returns a handle:

MemberWhat it does
dashboardIdThe dashboard id this mount shows.
setFilters(filters)Replaces the filters your app set. Returns a promise that resolves once the dashboard applied them; then filterChanged fires with source: 'host'. See Filters.
setTheme(theme)Replaces the theme tokens in place. The frame does not reload.
download(format, options)Downloads the dashboard as pdf, or one chart as csv (with chartId), exactly as the dashboard's own Download menu does, with the filters in effect in the dashboard. The SDK asks the dashboard, which downloads on its own session; your app sends no request. Rows are the user's own. Rejects with a DownloadError. See the guide's Add download buttons.
on(event, handler)Subscribes to an event. Returns a function that unsubscribes.
unmount()Removes the dashboard and cancels pending token requests and timers. Safe to call twice.

The setFilters promise always settles. It rejects with a SetFiltersError whose outcome is:

  • deadline: the filters were not applied within loadDeadlineMs. failed fires with reason network and the mount ends. A dashboard that is not displayed then (in a hidden tab) gets another loadDeadlineMs instead.
  • ended: the mount ended first, or had already ended.
  • replaced: a later setFilters call came before this one was applied.

If the dashboard refuses a name or a value, setFilters rejects with a FilterError instead and applies nothing. Its code is unknown_filter or invalid_filter_value, filterNames lists the refused names and knownFilterNames lists every filter the dashboard has. filterRejected fires with the same error. A call made before the first token arrives waits for it: the names are checked against the token's filter list then, in call order.

Tokens

Your page asks Jitto for a short-lived dashboard token with the signed-in user's own access token, the one your sign-in library already holds. That token is the user's identity: there is no token endpoint on your backend, no shared account and no secret, and your code never states who the user is or what they may see.

  1. The SDK calls your fetchToken(dashboardId, signal) on mount and again at 80 % of the token's lifetime (at most about 24.8 days later, the longest delay a browser timer holds).

  2. Your fetchToken sends the user's own access token to Jitto, from the browser:

    POST /api/v1/embed-tokens HTTP/1.1
    Host: example-tenant.jittocloud.com
    Origin: https://portal.client.example
    Authorization: Bearer <the user's own access token>
    Content-Type: application/json
    
    {"dashboard_id": "3f0c6a52-8d1e-4b7a-9c2f-5e8d71a4b6c0"}
    
  3. Jitto checks the token against your tenant's identity provider (issuer, signature, audience, expiry), reads the user's groups from it, and applies the row rules your tenant administrator stored in Jitto. It checks the Origin against your tenant's allowed origins, and answers the browser's CORS preflight only for those origins.

  4. Jitto answers with a dashboard token that lives 5 minutes. The answer has Cache-Control: no-store:

    {
      "token": "<opaque>",
      "dashboard_id": "3f0c6a52-8d1e-4b7a-9c2f-5e8d71a4b6c0",
      "embed_url": "https://example-tenant.jittocloud.com/embed",
      "expires_in": 300
    }
    
  5. Your fetchToken hands it to the SDK with the SDK's field names (dashboardId, embedUrl, expiresIn, and filterBar when the answer has filter_bar, the dashboard's filters), values unchanged. The SDK checks the answer itself and refuses a wrong one with reason token.

On an error Jitto answers with an HTTP status and a problem body (code, correlation_id), and with Retry-After on a 429. A token that is expired, for another audience or from another issuer is refused with 401 unauthenticated (reason token); a user in no group the row rules read gets 403 no_group (reason permission). Your tenant administrator registers your identity provider with Jitto once.

Your fetchToken rejects on an HTTP error with the status and the API's code. This exact code is tested against 401, 403, 422, 429, 502 and malformed answers. getAccessToken() is your sign-in library's:

async function fetchToken(dashboardId, signal) {
  const r = await fetch('https://example-tenant.jittocloud.com/api/v1/embed-tokens', {
    method: 'POST',
    signal,
    cache: 'no-store',
    headers: {
      Authorization: `Bearer ${await getAccessToken()}`, // the signed-in user's own token, from your sign-in library
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ dashboard_id: dashboardId }), // nothing else: Jitto reads who the user is from the token
  });
  const body = await r.json().catch(() => null);
  if (!r.ok) {
    const retryAfter = Number(r.headers.get('Retry-After'));
    throw Object.assign(new Error(`token request failed with HTTP ${r.status}`), {
      status: r.status,
      code: body?.code,
      correlationId: body?.correlation_id,
      retryAfterSeconds: retryAfter > 0 ? retryAfter : undefined,
    });
  }
  if (!body || typeof body !== 'object') return body; // the SDK refuses it with reason `token`
  const answer = { token: body.token, dashboardId: body.dashboard_id, embedUrl: body.embed_url };
  if ('expires_in' in body) answer.expiresIn = body.expires_in;
  if ('filter_bar' in body) answer.filterBar = body.filter_bar; // the dashboard's filters; without it, filters are refused
  return answer; // the SDK checks it and refuses a wrong answer with reason `token`
}

Events

Subscribe with the on… props or callbacks, or with handle.on(name, handler).

EventCallbackPayloadWhen
loadedonLoaded{ dashboardId, durationMs }The dashboard rendered for the first time after a mount.
failedonFailed{ dashboardId, reason, message, status?, code?, correlationId? }The dashboard could not load, a token refresh failed for good, or a setFilters reload did not render in time. Fires at most once per mount; the mount is then finished and the SDK shows a short Jitto message in its place. Mount again to retry.
filterChangedonFilterChanged{ dashboardId, filters, source }The filters in effect changed: once per applied setFilters call or filters prop change, with source: 'host'. 'user' (a change in the filter bar) is not reported yet.
filterRejectedonFilterRejected{ dashboardId, error }The dashboard refused filters from setFilters or a changed filters prop. error is the FilterError the call rejects with. Nothing was applied and the dashboard was not reloaded.
sizeChangedonSizeChanged{ dashboardId, width, height }The dashboard frame's size on the page changed. See Size.

reason is one of token, permission, network and not_found.

Size

Give the container a height, such as the window's height less your page's header. The dashboard frame takes the dashboard's own height within it:

  • never taller than the container: a taller dashboard scrolls inside the frame, so its filter bar, messages and dialogs stay on screen;
  • never shorter than the container or 600 px, whichever is less, so a short dashboard keeps room for its filter bar and dialogs. Below that, the frame ends where the dashboard does.

sizeChanged reports the frame's size on the page. You may size your container to it, so no space is left below a short dashboard; that never makes the frame taller than the container was.

Filters

Filters set what the dashboard shows first, by the names its filter bar uses (for example Ccy, Trade Date & Time), exactly, case included.

  • Set them on mount with filters, and later with setFilters (or a new filters prop).
  • The names come with the token: Jitto's answer lists the dashboard's filters in filter_bar, each a name and a kind, and your fetchToken passes it on as filterBar. Without it every filter is refused.
  • Send a filter only to dashboards that have it. A name the dashboard lacks is refused with a FilterError, and on mount the dashboard does not load; it is never ignored, so the dashboard never shows unfiltered data.
  • A value is a string, number or boolean, a list (several values), a date range { from, to } (two days written YYYY-MM-DD, such as 2026-06-30, from on or before to, both included), or null to clear the filter. Which values a filter takes depends on its kind: a list of values on a multiple-choice filter, exactly two numbers [min, max] on a numeric range, a date range only on a date-range filter.
  • A list takes at most 1 000 values and a string at most 256 characters, the limits of the dashboard and of Jitto's export API, so download() is never refused for them. A value over them is refused with invalid_filter_value, and so is a call that sets more than 50 filters. A filter whose name has more than 255 characters cannot be set.
  • setFilters replaces every filter your app set: a name you leave out goes back to the dashboard's own default.
  • The filters travel in the dashboard's address, by name, which holds at most 4 096 bytes of them once encoded. A call that needs more (a long list on a multiple-choice filter) is refused at once with invalid_filter_value, naming the filters too long on their own (name and value), else its list-valued filters, else every filter.
  • Like any address, the dashboard's can appear in your Jitto host's access logs, filter names and values included. Pass the values a user picks (a currency, a date), never a secret or personal data.
  • download() gives the file with the filters in effect in the dashboard: those your app set, and the user's own filter-bar changes made since.
  • Filters narrow the view. They are not security. A user can change them in the filter bar. What a user may see is decided only by the row rules in Jitto.
  • Today setFilters reloads the dashboard inside the frame. Your page stays mounted and no new token is requested, but the user's own filter-bar changes and scroll position are reset. Set filters on mount when you can.

Theme

Theme tokens change colours, font and light or dark. Jitto keeps the layout; there is no CSS hook.

TokenWhat it changes
mode'light' or 'dark'.
colorPrimaryButtons, selected states, links.
colorBackgroundThe dashboard's page background.
colorSurfaceChart and filter card background.
colorTextBody text.
colorBorderBorders and dividers.
fontFamilyA CSS font-family list. The font must already be loadable by the dashboard host.

Colours are CSS colour strings (#1a73e8, rgb(…)). Anything else is ignored with a console warning. setTheme never reloads the frame.

Errors and troubleshooting

Every failure arrives as one failed event with one of four reasons. status, code and correlationId come from the API when it answered; quote the correlationId when you ask Jitto for help.

ReasonCommon causesWhat to do
tokenThe user's token was refused (401 unauthenticated: expired, wrong audience, wrong issuer); too many token requests (429 rate_limited, retried first); no token within tokenDeadlineMs; an answer for another dashboard or with an embedUrl that is not your embedOrigin followed by /embed.Check the user is signed in and your sign-in library returns a current token. Check embedOrigin. Request a token once per page view and let the SDK refresh.
permissionThe dashboard is not on your tenant's embeddable list (403 not_embeddable); your origin is not allowed (403 origin_not_allowed); the user is in no group the row rules read (403 no_group); a dataset of the dashboard has no row rule (422 row_rules_incomplete); embedding is not switched on (503 embedding_not_configured).Ask your tenant administrator to fix the setting the code names.
not_foundNo dashboard with this id (404 unknown_dashboard).Check dashboardId.
networkNo answer, a server error (500, 502, retried first), a refused CORS preflight, or the frame stayed silent for loadDeadlineMs. A CORS error on the token request, or a frame-ancestors error, in the browser console means the page's origin is not allowed.Check the network. For a CORS or frame-ancestors error, ask for your origin to be added exactly as the browser shows it (localhost and 127.0.0.1 differ, and so do ports).

Other symptoms:

You seeCauseFix
The dashboard loads with no rowsThe user's groups match no row ruleAsk your tenant administrator which groups the row rules read.
A blank area with no errorThe container has no heightGive the container (style or className) a height.
Your own CSP blocks the frame or the token requestYour frame-src or connect-src does not allow your tenant's Jitto hostAdd your embedOrigin (for example https://example-tenant.jittocloud.com) to both frame-src and connect-src.

Supported browsers and frameworks

Proposed for 1.0.0; the end-to-end tests confirm the list before release.

  • Browsers: the current and previous major versions of Chrome, Edge, Firefox and Safari. Pages must be served over https://, including local development (for example https://localhost:5173).
  • React: 18 and 19, through @jitto/analyze-embed/react.
  • Everything else (Angular, Vue, server-rendered pages, no framework): use mountJittoDashboard from @jitto/analyze-embed. Dedicated wrappers may follow on the same core.
  • TypeScript: declarations are included. Every option, event and payload is typed.

Versioning

  • The package follows semantic versioning. A breaking change to an option, event, payload or failure reason is a new major version.
  • The package's major version matches the embed API's path version: @jitto/analyze-embed 1.x talks to /api/v1/ only. Additive API changes stay in v1.
  • A deprecated API version answers with Deprecation, Sunset and Link headers for at least 90 days before it is removed.

Full guide and API reference

  • The embedding guide, GUIDE.md in this package: the whole flow, the tenant administrator's settings, and a security checklist.
  • The API reference, generated from the embed API's OpenAPI 3.1 contract. Your Jitto contact provides it with your tenant.

Both move to the Jitto docs site before 1.0.0, and this section then links there.

版本与发布记录

@jitto/analyze-embed 共发布 4 个版本,首次发布于 2026/10/27;过去 12 个月发布了 4 个版本。

版本发布日期距今
0.4.0latest2026/11/011 天前
0.3.02026/10/304 天前
0.2.02026/10/277 天前
0.0.0-stage2026/10/277 天前

关于 @jitto/analyze-embed 的常见问题

@jitto/analyze-embed 是什么?

在你的web应用中嵌入Jitto仪表板,用React或纯JavaScript。

如何安装 @jitto/analyze-embed?

在项目目录运行 npm install @jitto/analyze-embed,或使用 pnpm add / yarn add / bun add。

@jitto/analyze-embed 支持 TypeScript 吗?

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

@jitto/analyze-embed 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@jitto/analyze-embed 可以商用吗?

@jitto/analyze-embed 使用 SEE LICENSE IN LICENSE 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@jitto/analyze-embed 还在维护吗?

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