Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/rules/demos-pages.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Source of truth: `demos/README.md` and `demos/assemble-pages.sh`. Keep the READM

## Token

- Client-only: OAT in memory only; send only as `x-api-key` to hosts in `config` / OpenAPI `servers`.
- Client-only: OAT in memory only; send only as `x-api-key` to hosts in `config` / OpenAPI `servers` (`api.robotemi.com`, `api.robotemi.cn`, `integration.dev.temi.cloud`). Use the production host that matches the organization's region.
- No `sessionStorage` / `localStorage` / cookies / analytics / CDN scripts that can read the token.
- `fetch` that carries the OAT: `redirect: 'error'`.
- Do not official-host a backend that accepts someone else's OAT.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Usage

Authenticate every request with an Organization Access Token (OAT) in the `x-api-key` header.

Use `https://api.robotemi.com/openapi/v1` (Production) or
`https://api.robotemi.cn/openapi/v1` (Production-CN) for the same region as
the organization.

OpenAPI control requires a **PRO** (or PRO free-trial) robot.

```
Expand Down
2 changes: 1 addition & 1 deletion demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ An OAT is a secret. Demos in this repository must not collect, store, or transmi

Acceptable patterns include:

- **Client-only apps.** The token is entered by the user in the browser and is sent only to temi APIs. It must not leave the browser for any other destination (no analytics, logging, or third-party backends).
- **Client-only apps.** The token is entered by the user in the browser and is sent only to temi APIs. It must not leave the browser for any other destination (no analytics, logging, or third-party backends). Use Production (`api.robotemi.com`) or Production-CN (`api.robotemi.cn`) for the same region as the organization.
- **User-deployed serverless.** A small service the user can deploy themselves, with the token supplied at runtime as a parameter or environment variable. Do not bake tokens into source, config, or hosted defaults.

Do not:
Expand Down
2 changes: 1 addition & 1 deletion demos/mvp-frontend-only/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Client-only demo of the [temi OpenAPI](https://openapi-docs.robotemi.com). No build step, no backend.

The Organization Access Token is typed in the browser and sent only as `x-api-key` to `api.robotemi.com` or `integration.dev.temi.cloud`. It is kept in memory for this page load only — not written to `sessionStorage` or `localStorage`.
The Organization Access Token is typed in the browser and sent only as `x-api-key` to the selected OpenAPI host: `api.robotemi.com` (Production), `api.robotemi.cn` (Production-CN), or `integration.dev.temi.cloud` (Integration). Use the production host that matches the organization's region. The token is kept in memory for this page load only — not written to `sessionStorage` or `localStorage`.

## What it does

Expand Down
1 change: 1 addition & 0 deletions demos/mvp-frontend-only/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ <h1>temi OpenAPI — frontend-only demo</h1>
<label for="env">Environment</label><br>
<select id="env">
<option value="production">production (api.robotemi.com)</option>
<option value="production-cn">production-cn (api.robotemi.cn)</option>
<option value="integration">integration (integration.dev.temi.cloud)</option>
</select>
</p>
Expand Down
1 change: 1 addition & 0 deletions demos/mvp-frontend-only/js/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** API hosts from temi-partner.openapi.yaml `servers`. */
export const ENVIRONMENTS = {
production: 'https://api.robotemi.com/openapi/v1',
'production-cn': 'https://api.robotemi.cn/openapi/v1',
integration: 'https://integration.dev.temi.cloud/openapi/v1',
}

Expand Down
13 changes: 9 additions & 4 deletions demos/temi-openapi-scenario-runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ It demonstrates the complete operator flow:
`verify → discover → validate → play → poll → stop`

The interface starts in Chinese and includes an English option. It supports the
Production and Integration temi API environments, robot selection, MOVEMENT,
SPEAK, and START_CALL actions, final run confirmation, and status polling.
Production (`api.robotemi.com`), Production-CN (`api.robotemi.cn`), and
Integration temi API environments, robot selection, MOVEMENT, SPEAK, and
START_CALL actions, final run confirmation, and status polling.

Choose the production environment that matches the organization's region.

## Run locally

Expand All @@ -32,8 +35,10 @@ The demo is served over HTTP; do not open the source or `dist/index.html` with

Enter your own Organization Access Token (OAT) in the page. The demo keeps it
only in page memory and sends it as `x-api-key` directly to the selected temi
API origin. It does not use a backend, cookies, Web Storage, analytics, or
third-party runtime code. The token is cleared when the connection is reset,
API origin (`api.robotemi.com`, `api.robotemi.cn`, or
`integration.dev.temi.cloud`). Use the production host that matches the
organization's region. It does not use a backend, cookies, Web Storage,
analytics, or third-party runtime code. The token is cleared when the connection is reset,
the environment changes, a request returns 401, or the page closes.

Never commit an OAT, put one in a URL, or include one in logs, screenshots, or
Expand Down
2 changes: 1 addition & 1 deletion demos/temi-openapi-scenario-runner/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'; object-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self' https://api.robotemi.com https://integration.dev.temi.cloud"
content="default-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'; object-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self' https://api.robotemi.com https://api.robotemi.cn https://integration.dev.temi.cloud"
/>
<title>场景运行 | temi Developer</title>
<link rel="stylesheet" href="./src/styles.css" />
Expand Down
5 changes: 3 additions & 2 deletions demos/temi-openapi-scenario-runner/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export const ENVIRONMENTS = ["production", "integration"] as const;
export const ENVIRONMENTS = ["production", "production-cn", "integration"] as const;
export type Environment = (typeof ENVIRONMENTS)[number];

export const ENVIRONMENT_BASE_URLS: Readonly<Record<Environment, string>> = Object.freeze({
production: "https://api.robotemi.com/openapi/v1",
"production-cn": "https://api.robotemi.cn/openapi/v1",
integration: "https://integration.dev.temi.cloud/openapi/v1",
});

Expand Down Expand Up @@ -211,7 +212,7 @@ export class TemiApiError extends Error {
}

export function isEnvironment(value: unknown): value is Environment {
return value === "production" || value === "integration";
return typeof value === "string" && (ENVIRONMENTS as readonly string[]).includes(value);
}

export class TemiApiClient {
Expand Down
18 changes: 12 additions & 6 deletions demos/temi-openapi-scenario-runner/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,7 @@ let discovery: RobotDiscoveryController | null = null;
let composer: SequenceComposer | null = null;
let runState: RunStateSnapshot = createRunState();
let lifecycle: PlayPollStopLifecycle | null = null;
let environmentTransport: Record<Environment, EnvironmentTransportState> = {
production: "unknown",
integration: "unknown",
};
let environmentTransport: Record<Environment, EnvironmentTransportState> = emptyEnvironmentTransport();

let pendingRobotSerialNumber: string | null = null;
const robotStatusPanels = new Map<string, RobotStatusPanelState>();
Expand Down Expand Up @@ -1747,7 +1744,7 @@ function resetPage(message: string): void {
composer = null;
clearConnection();
runState = createRunState();
environmentTransport = { production: "unknown", integration: "unknown" };
environmentTransport = emptyEnvironmentTransport();
pendingRobotSerialNumber = null;
robotResourcesLoading = false;
editingValidatedSequence = false;
Expand Down Expand Up @@ -1954,7 +1951,16 @@ function statusLabelFor(status: RobotStatus["status"] | undefined, targetLanguag
}

function environmentLabel(value: Environment): string {
return value === "production" ? "Production" : "Integration";
if (value === "production") return copy("生产", "Production");
if (value === "production-cn") return copy("生产(中国)", "Production-CN");
return copy("集成", "Integration");
}

function emptyEnvironmentTransport(): Record<Environment, EnvironmentTransportState> {
return Object.fromEntries(ENVIRONMENTS.map((value) => [value, "unknown"])) as Record<
Environment,
EnvironmentTransportState
>;
}

function displayName(value: string | undefined, fallback: string): string {
Expand Down
6 changes: 5 additions & 1 deletion temi-partner.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ info:

Authenticate every request with an Organization Access Token (OAT) in the
`x-api-key` header. Tokens are scoped; each endpoint requires the permission
noted in its description.
noted in its description. Call the production host that matches the
organization: Production (`api.robotemi.com`) or Production-CN
(`api.robotemi.cn`).

OpenAPI control requires a **PRO** (or PRO free-trial) robot, matching launcher
SDK access. `GET /robots` omits BASIC robots. Robot-scoped endpoints return
Expand All @@ -23,6 +25,8 @@ info:
servers:
- url: https://api.robotemi.com/openapi/v1
description: Production
- url: https://api.robotemi.cn/openapi/v1
description: Production-CN
- url: https://integration.dev.temi.cloud/openapi/v1
description: Integration
tags:
Expand Down