# 開發登入 Mini App <Badge type="warning" text="尚未發布" />

從零做出一個 Mini App：用這個人的 Micromeet 帳號讓他登入，再從它自己的後端以這個人的身分呼叫 Micromeet。每個函式的說明請見[登入](https://docs.micromeet.ai/zh-tw/miniapp-sdk/auth.md)，打包請見[打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md)，封裝的全部規則請見[封裝規範](https://docs.micromeet.ai/zh-tw/miniapp-sdk/package-spec.md)。

## 整體流程

從第一次建置到使用者開啟 Mini App，誰做什麼：

```mermaid
flowchart TB
  subgraph dev["你"]
    build["vite build<br/>miniapp-package/"]
    verify["micromeet-miniapp verify"]
    zip["micromeet-miniapp zip<br/>已簽章的 zip"]
    file["在後端網域上<br/>放驗證檔案"]
  end
  subgraph mm["Micromeet"]
    reg["登記 AppID<br/>每個區一組 client secret"]
    dom["驗證伺服器網域"]
    sign["登記並簽章這個版本<br/>核對每個檔案和網域"]
  end
  subgraph cw["Cowork"]
    install["安裝"]
    open{"簽章有效、<br/>檔案沒改過？"}
    run["開啟 Mini App"]
    refuse["提示未經認證，<br/>不開啟"]
  end
  reg --> file --> dom
  build --> verify --> sign
  dom --> sign
  sign -->|簽好的 manifest.json| zip --> install --> open
  open -->|是| run
  open -->|否| refuse
```

Cowork 每次開啟都驗證簽章，頁面每讀一個檔案都核對；檔案被改過就關閉這個 Mini App。

使用者點了你的登入按鈕之後：

```mermaid
sequenceDiagram
  actor P as 使用者
  participant Page as 頁面
  participant C as Cowork
  participant M as Micromeet
  participant B as 你的後端
  P->>Page: 點登入
  Page->>C: auth.login()
  C->>M: 這個 Mini App 是誰、能請求什麼
  C->>P: 確認卡（第一次）：Mini App、scope、帳號
  P->>C: 授權
  C->>M: 為這個版本和這個人要 code
  Note over M: AppID 啟用、版本已登記、<br/>檔案一致、有驗證過的伺服器網域、scope 允許
  M-->>C: code（5 分鐘，只能用一次）
  C-->>Page: code 和 region
  Page->>B: code 和 region
  B->>M: code 和 client secret
  M-->>B: access token、refresh token、user.ref
  B->>M: 用 access token 讀取 profile
  M-->>B: 姓名、電子郵件、機構
  B-->>Page: 你自己的工作階段
```

頁面始終拿不到密碼、token 和 client secret。下面的步驟逐一做出每個部分。

## 1. 請 Micromeet 登記這個 Mini App

無法自助登記。把這些資料寄給 Micromeet：

| 內容 | 範例 |
|---|---|
| AppID：小寫反向網域 | `ai.example.app` |
| 名稱（英文；中文選填）和開發方 | Example · Example Ltd |
| 需要的 scope | `profile:read` |
| 登入使用者所在的區（SG、HK、ID） | HK |
| 後端的位址 | `https://api.example.com` |

每個區給你一組 **client secret**，只顯示一次：存進後端的金鑰儲存區。每個後端位址還會給你一個驗證檔案（第 2 步）。

## 2. 證明後端網域屬於你

把這個檔案放在 `https://api.example.com/micromeet-miniapp-<16 個字元>.txt`，用 HTTPS 回傳 200，不能轉址，並一直保留；Micromeet 取回它來驗證網域。開發時，後端可以跑在 `http://localhost:<連接埠>` 或 `http://127.0.0.1:<連接埠>`，在 Micromeet 允許的區不需要放檔案。請見[打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md)裡的「伺服器網域」。

## 3. 撰寫頁面

安裝 `@micromeet-ai/miniapp-sdk`（請見[打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md)裡的「建置」），然後：

```js
import { maestroSdk } from '@micromeet-ai/miniapp-sdk';

const BACKEND = 'https://api.example.com';      // 驗證過的伺服器網域

export async function signIn() {
  const sdk = maestroSdk();                     // 不在 Cowork 裡時為 null
  if (!sdk?.auth) return { error: 'open_in_cowork' };
  const login = await sdk.auth.login({ scopes: ['profile:read'] });
  if (!login.ok) return { error: login.error };
  const response = await fetch(`${BACKEND}/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code: login.code, region: login.region })
  });
  return response.json();                       // { session, name }：之後的請求帶上 session
}
```

- 在使用者點擊時呼叫 `auth.login()`：Cowork 只在使用者正在看的分頁上跳出確認卡。
- `auth.status()` 說明封裝是否已簽章、Cowork 是否有人登入，可以用來決定是否顯示登入按鈕。
- 腳本都放在檔案裡：內嵌 `<script>` 不會執行。

## 4. 撰寫後端

一個最小的 Node.js 後端：

```js
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import { createMiniappAuthClient, MiniappAuthError } from '@micromeet-ai/miniapp-sdk/server';

const APP_ID = 'ai.example.app';
const PAGE = `miniapp://${APP_ID}`;             // 頁面的 origin
// 每個區一個 client，用那個區的 secret。
const clients = { HK: createMiniappAuthClient({ appId: APP_ID, clientSecret: process.env.MINIAPP_SECRET_HK }) };
const sessions = new Map();                     // 實際存進你的資料庫

const reply = (res, status, body) => {
  res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': PAGE, Vary: 'Origin' });
  res.end(JSON.stringify(body));
};

createServer((req, res) => {
  if (req.method === 'OPTIONS') {
    res.writeHead(204, { 'Access-Control-Allow-Origin': PAGE, 'Access-Control-Allow-Methods': 'POST', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' });
    return res.end();
  }
  if (req.method !== 'POST' || req.url !== '/login') return reply(res, 404, { error: 'not_found' });
  let raw = '';
  req.on('data', (part) => (raw += part));
  req.on('end', async () => {
    try {
      const { code, region } = JSON.parse(raw);
      const auth = clients[region];
      if (!auth) return reply(res, 400, { error: 'region_not_supported' });
      const tokens = await auth.exchangeCode({ code, region });
      const session = randomUUID();
      sessions.set(session, { ref: tokens.user.ref, region, refreshToken: tokens.refreshToken });
      reply(res, 200, { session, name: tokens.user.name });
    } catch (error) {
      reply(res, 400, { error: error instanceof MiniappAuthError ? error.code : 'bad_request' });
    }
  });
}).listen(8787);
```

- 回傳 `Access-Control-Allow-Origin: miniapp://<appId>`：這是頁面的 origin，少了這個回應標頭，請求在頁面裡就失敗了，到不了你的程式。
- 用 `user.ref` 當使用者的鍵，保存好 refresh token，之後要呼叫 Micromeet 時用 `refresh` 換新的 token。
- 頁面只拿到你自己的工作階段；Micromeet 的 token 留在後端。
- 部署在你驗證過的伺服器網域上，開發時跑在 localhost。其他語言的後端做同樣的呼叫（請見[登入](https://docs.micromeet.ai/zh-tw/miniapp-sdk/auth.md)裡的「HTTP 呼叫」）。

## 5. 打包、簽章、安裝

1. 在 Vite 外掛裡寫 `domains: ['https://api.example.com']`，然後 `vite build`（請見[打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md)）。
2. 執行 `micromeet-miniapp verify miniapp-package`，再把封裝交給 Micromeet；Micromeet 在每個區登記並簽章這個版本，交回簽好的 `manifest.json`，把它放回封裝裡。
3. 執行 `micromeet-miniapp verify miniapp-package --signed`，然後 `micromeet-miniapp zip miniapp-package`。
4. 在 Cowork 裡請 Agent 安裝這個 zip，開啟 Mini App，點登入。

正式版 Cowork 只開啟 Micromeet 正式環境簽章的版本；沒有免簽章的開發模式。

## 6. 開發與測試

- 不需要登入的畫面和邏輯：在瀏覽器裡 `vite dev`。那裡 `maestroSdk()` 是 `null`，顯示一句提示代替登入按鈕。
- 只有頁面的檔案會被簽章：改後端不需要新版本。頁面盡量簡單，邏輯放在後端。
- 改了頁面的任何檔案或 `domains`，都要換新的 `version`，重新登記、簽章、安裝。
- 端到端測試登入需要：簽好的版本，加上登入了已登記區帳號的 Cowork。
- 開發用的版本可以只寫 localhost 網域；發布的版本不要帶 localhost：不允許 localhost 的區會因此拒絕登入（`no_server_domain`）。

## 出錯怎麼辦

| 在哪裡 | 看到什麼 | 原因和處理 |
|---|---|---|
| Cowork | 提示這個 Mini App 未經認證，無法開啟 | 封裝沒有簽章、由別的 Micromeet 環境簽章，或簽章後改過檔案：安裝簽好的 zip |
| 頁面 | `app_not_available` | 使用者所在的區沒有登記這個 AppID，或已停用 |
| 頁面 | `version_not_available` | 這個版本沒有在那個區登記，或已停用 |
| 頁面 | `no_server_domain` | 這個版本在那個區沒有驗證過的伺服器網域，或驗證檔案不在了 |
| 頁面 | `scope_denied` | 請求了 Micromeet 沒有給這個 Mini App 的 scope |
| 頁面 | `fetch` 出現跨來源（CORS）錯誤 | 後端沒有回傳 `Access-Control-Allow-Origin: miniapp://<appId>` |
| 後端 | `invalid_client` | 這個區的 secret 不對，或已被更換 |
| 後端 | `invalid_grant` | code 已經用過、超過 5 分鐘，或不是這個 AppID 的 |

## 相關

- [登入](https://docs.micromeet.ai/zh-tw/miniapp-sdk/auth.md): 全部登入函式、後端工具和錯誤
- [打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md): 做出封裝並交給 Micromeet 簽章
- [封裝規範](https://docs.micromeet.ai/zh-tw/miniapp-sdk/package-spec.md): 封裝必須符合的全部規則
