跳到正文
Indiana
控制台
文档
章节目录 / App、环境与身份

App、环境与身份

理解组织、App、客户与两类凭据的边界。

SDK 0.0.1更新于

App、环境与客户

客户按 App、环境和宿主 externalUserId 唯一。Sandbox 与 Live 独立;控制台成员和 SDK 客户是不同身份。范围头只核对凭据,不授予权限或切换 App。游客由后端持有游客凭证,支付前完成真实认证。

会话与登出

  • Token 只存在 SDK 内存。过期后向自己的已认证后端刷新;SDK 不自动刷新。
  • 登出或切换账号时先销毁展示并 disconnectSession,中止尚未完成的宿主会话请求,避免旧 Token 再次 identify。重试写入复用原 requestId / 幂等键。
  • 宿主后端可以撤销单个会话或客户的全部会话;撤销签发 Key 后,客户 Token 在下次请求失效。

相关:服务端 Key 的校验与轮换

lib/groove-session.tsTypeScript
import { GrooveError } from "@tinkerlandai/indiana-sdk";import { grow } from "./groove-client"; // 向你自己的服务端取新 Token(会话接口需要你的登录态)let generation = 0;let pending: { controller: AbortController; promise: Promise<void> } | undefined; function refreshSession(): Promise<void> {  if (pending) return pending.promise;  const expected = generation;  const controller = new AbortController();  const promise = (async () => {    const response = await fetch("/api/groove-session", {      signal: controller.signal, cache: "no-store", credentials: "same-origin",    });    if (!response.ok) throw new Error("用户未登录");    const { customerToken } = await response.json();    if (expected !== generation)      throw new GrooveError("IDENTITY_CHANGED", "Identity changed during refresh");    if (typeof customerToken !== "string") throw new Error("Invalid session");    grow.identify(customerToken);  })();  pending = { controller, promise };  const clear = () => { if (pending?.promise === promise) pending = undefined; };  void promise.then(clear, clear);  return promise;} // Token 过期(默认 10 分钟)时重新签发一次再重试export async function withSession<T>(call: () => Promise<T>) {  const expected = generation;  try {    return await call();  } catch (error) {    if (!(error instanceof GrooveError) || error.code !== "UNAUTHENTICATED")      throw error;    if (expected !== generation) throw new GrooveError("IDENTITY_CHANGED", "Identity changed");    await refreshSession();    if (expected !== generation) throw new GrooveError("IDENTITY_CHANGED", "Identity changed");    return call();  }} // 你的用户退出登录时:先丢弃 Token,再走你自己的登出export function disconnectSession() {  generation++;  pending?.controller.abort();  pending = undefined;  grow.reset(); // 清除客户 Token,中止进行中的请求} export async function signOut() {  disconnectSession();  await fetch("/api/logout", { method: "POST" });}