SDK API อ้างอิง

SDK API อ้างอิง

SDK ทางการเป็นสองแพ็กเกจที่ไม่ผูกกับเฟรมเวิร์กและไม่มี dependency ในรันไทม์ สามารถรับซอร์สโค้ดได้ที่ GitHub ที่อยู่: https://github.com/gptbots/workspace-extension-sdk

ชื่อแพ็กเกจ ตำแหน่งที่รัน จุดเข้า
@gptbots/workspace-extension-verify แบ็กเอนด์(Node ≥ 18) verifyWsa, exchangeWorkspaceCode
@gptbots/workspace-extension-sdk เบราว์เซอร์ consumeHandoff, startWorkspaceLogin, completeWorkspaceLogin

ทั้งสองแพ็กเกจเป็น ESM"type": "module")โปรเจกต์ CommonJS โปรดใช้ import() แบบไดนามิก หรือเปลี่ยนไปใช้ ESM


แพ็กเกจแบ็กเอนด์ @gptbots/workspace-extension-verify

verifyWsa(token, options): WorkspaceIdentity

ตรวจสอบ wsa และคืนค่าตัวตนในพื้นที่ทำงาน ลำดับการตรวจสอบ: ลายเซ็น → issaud → หมดอายุ(exp±leeway, รวม iat/nbf) → claim ที่จำเป็น

interface VerifyOptions { secret?: string; // คีย์ลับ HS256 (per-app หรือแบบร่วม) จำเป็นเมื่อตรวจสอบ HS256 publicKey?: string; // กุญแจสาธารณะ RS256 PEM จำเป็นเมื่อตรวจสอบ RS256 (แผนงาน) audience: string; // host ของแอปส่วนขยาย ต้องเท่ากับ aud ในโทเค็น (จำเป็น) issuer?: string; // ค่าเริ่มต้น 'gptbots-workspace' leewaySeconds?: number; // ผ่อนผันการคลาดเคลื่อนของนาฬิกา (วินาที) ค่าเริ่มต้น 30 algorithms?: ('HS256' | 'RS256')[]; // ค่าเริ่มต้น ['HS256'] } interface WorkspaceIdentity { accountId: string; // = JWT sub role: 'OWNER' | 'ADMIN' | 'MEMBER'; // ค่าที่ไม่รู้จักจะถูกทำให้เป็น MEMBER workspaceId: string; // = JWT workspace_id username?: string; email?: string; avatar?: string; appName?: string; issuedAt?: number; // = iat (วินาที) expiresAt?: number; // = exp (วินาที) }
                      
                      interface VerifyOptions {
  secret?: string;        // คีย์ลับ HS256 (per-app หรือแบบร่วม) จำเป็นเมื่อตรวจสอบ HS256
  publicKey?: string;     // กุญแจสาธารณะ RS256 PEM จำเป็นเมื่อตรวจสอบ RS256 (แผนงาน)
  audience: string;       // host ของแอปส่วนขยาย ต้องเท่ากับ aud ในโทเค็น (จำเป็น)
  issuer?: string;        // ค่าเริ่มต้น 'gptbots-workspace'
  leewaySeconds?: number; // ผ่อนผันการคลาดเคลื่อนของนาฬิกา (วินาที) ค่าเริ่มต้น 30
  algorithms?: ('HS256' | 'RS256')[]; // ค่าเริ่มต้น ['HS256']
}

interface WorkspaceIdentity {
  accountId: string;                  // = JWT sub
  role: 'OWNER' | 'ADMIN' | 'MEMBER'; // ค่าที่ไม่รู้จักจะถูกทำให้เป็น MEMBER
  workspaceId: string;                // = JWT workspace_id
  username?: string; email?: string; avatar?: string; appName?: string;
  issuedAt?: number;   // = iat (วินาที)
  expiresAt?: number;  // = exp (วินาที)
}

                    
บล็อกโค้ดนี้ในหน้าต่างลอย
  • บังคับ exp: ขาดหายหรือไม่ใช่ตัวเลขจำกัด → โยน MissingClaim(จะไม่ถูกถือว่า「ไม่มีวันหมดอายุ」)
  • iat / nbf(หากมี)อยู่ในอนาคตเกิน leeway → โยน NotYetValid
  • ล้มเหลวโยน WsaVerificationError(มี .code); ผู้เรียกคอนฟิกผิด(เช่น ขาดคีย์ลับ)โยน TypeError

ค่าของ WsaVerificationError.code:

code ความหมาย
InvalidToken โทเค็นว่าง / โครงสร้างไม่ถูกต้อง / segment ไม่ใช่ออบเจกต์ JSON
InvalidSignature ลายเซ็นไม่ตรง(คีย์ลับผิด, ถูกดัดแปลง)
Expired หมดอายุแล้ว(ก่อน exp + leeway
NotYetValid iat/nbf อยู่ในอนาคต(เกิน leeway)
WrongIssuer iss ≠ ค่าที่คาดหวัง
WrongAudience aud ≠ audience ของคุณ
MissingClaim ขาด exp / sub / role / workspace_id
UnsupportedAlgorithm alg ไม่อยู่ใน whitelist(ค่าเริ่มต้นเฉพาะ HS256

exchangeWorkspaceCode(options): Promise<WorkspaceCodeExchangeResult>

ใช้กับการเข้าสู่ระบบแบบ Pull: ที่แบ็กเอนด์ของแอปส่วนขยายให้แลก code ใช้ครั้งเดียว + PKCE codeVerifier เป็น wsa ห้ามเรียกในเบราว์เซอร์เด็ดขาด

interface ExchangeWorkspaceCodeOptions { tokenUrl: string; // endpoint /token ของแพลตฟอร์ม (absolute URL) code: string; // authorization code ใช้ครั้งเดียวที่ได้จาก callback codeVerifier: string; // PKCE verifier ที่ตรงกับ code_challenge fetch?: typeof fetch; // inject fetch ได้ (ทดสอบ/รันไทม์เก่า); ค่าเริ่มต้น fetch ส่วนกลาง timeoutMs?: number; // timeout ของคำขอ ค่าเริ่มต้น 10000; ส่ง 0 เพื่อปิด } interface WorkspaceCodeExchangeResult { wsa: string; // wsa ที่ลงลายเซ็นแล้ว ส่งให้ verifyWsa tokenType?: string; // 'Bearer' expiresIn?: number; // อายุ wsa (วินาที) }
                      
                      interface ExchangeWorkspaceCodeOptions {
  tokenUrl: string;      // endpoint /token ของแพลตฟอร์ม (absolute URL)
  code: string;          // authorization code ใช้ครั้งเดียวที่ได้จาก callback
  codeVerifier: string;  // PKCE verifier ที่ตรงกับ code_challenge
  fetch?: typeof fetch;  // inject fetch ได้ (ทดสอบ/รันไทม์เก่า); ค่าเริ่มต้น fetch ส่วนกลาง
  timeoutMs?: number;    // timeout ของคำขอ ค่าเริ่มต้น 10000; ส่ง 0 เพื่อปิด
}

interface WorkspaceCodeExchangeResult {
  wsa: string;           // wsa ที่ลงลายเซ็นแล้ว ส่งให้ verifyWsa
  tokenType?: string;    // 'Bearer'
  expiresIn?: number;    // อายุ wsa (วินาที)
}

                    
บล็อกโค้ดนี้ในหน้าต่างลอย
  • ส่งข้อมูลล้มเหลว / ไม่ใช่ 2xx / ธุรกิจ code≠0(เช่น 403209 invalid_grant, 403210 invalid_verifier)/ ขาด data.wsa → โยน Error
  • timeout โยน token exchange timed out after <ms>ms(ค่าเริ่มต้น 10s, กันแพลตฟอร์มตอบช้าค้างแบ็กเอนด์ของคุณ)

ตัวอย่าง Middleware ของ Express

import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify'; export function requireWorkspaceIdentity(secret: string, audience: string) { return (req, res, next) => { try { req.identity = verifyWsa(req.body.wsa, { secret, audience }); next(); } catch (e) { const code = e instanceof WsaVerificationError ? e.code : 'Error'; res.status(401).json({ code }); } }; }
                      
                      import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';

export function requireWorkspaceIdentity(secret: string, audience: string) {
  return (req, res, next) => {
    try {
      req.identity = verifyWsa(req.body.wsa, { secret, audience });
      next();
    } catch (e) {
      const code = e instanceof WsaVerificationError ? e.code : 'Error';
      res.status(401).json({ code });
    }
  };
}

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

แพ็กเกจเบราว์เซอร์ @gptbots/workspace-extension-sdk

แบบ Push(Push handoff)

readHandoffToken(search?, paramName?): string | null

ฟังก์ชันบริสุทธิ์ อ่าน wsa ดั้งเดิมจาก query string search ค่าเริ่มต้น location.search, paramName ค่าเริ่มต้น 'wsa'

stripHandoffToken(paramName?, ctx?): void

ใช้ history.replaceState ลบ wsa ออกจาก URL ปัจจุบัน เพื่อไม่ให้ตกค้างในแถบที่อยู่/ประวัติ/Referer นอกเบราว์เซอร์เป็น no-op ที่ปลอดภัย ctx?: { history?, location? } inject ได้(ทดสอบ)

consumeHandoff(options): Promise<WorkspaceIdentity>

ขั้นตอนสะดวกระดับ use: อ่าน wsa → POST ไปยังแบ็กเอนด์ของแอปส่วนขยาย → เมื่อสำเร็จลบ wsa → คืนค่าตัวตน

interface ConsumeHandoffOptions { exchangeUrl: string; // อินเทอร์เฟซตรวจสอบของแบ็กเอนด์แอปส่วนขยาย fetch?: typeof fetch; // inject ได้ search?: string; // ค่าเริ่มต้น location.search paramName?: string; // ค่าเริ่มต้น 'wsa' strip?: boolean; // ค่าเริ่มต้น true (ลบเฉพาะเมื่อสำเร็จ) }
                      
                      interface ConsumeHandoffOptions {
  exchangeUrl: string;   // อินเทอร์เฟซตรวจสอบของแบ็กเอนด์แอปส่วนขยาย
  fetch?: typeof fetch;  // inject ได้
  search?: string;       // ค่าเริ่มต้น location.search
  paramName?: string;    // ค่าเริ่มต้น 'wsa'
  strip?: boolean;       // ค่าเริ่มต้น true (ลบเฉพาะเมื่อสำเร็จ)
}

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

โทเค็นจะถูกลบหลังแลกสำเร็จเท่านั้น เพื่อให้ความล้มเหลวชั่วคราวสามารถรีเฟรชลองใหม่ได้ หากต้องการให้ลบทันทีแม้ล้มเหลว ให้ catch แล้ว stripHandoffToken() เอง

แบบ Pull(Pull / M-Auth)

startWorkspaceLogin(options): Promise<WorkspaceLoginRequest>

สร้าง PKCE, เก็บใน sessionStorage, สร้างและ(โดยค่าเริ่มต้น)เปลี่ยนเส้นทางไปที่ /authorize ต้องอยู่ในบริบทที่ปลอดภัย(HTTPS/localhost)

interface StartWorkspaceLoginOptions { authorizeUrl: string; // endpoint /authorize ของแพลตฟอร์ม (absolute URL) clientId: string; // app home URL ของแอปส่วนขยาย redirectUri: string; // ที่อยู่ callback, host ต้องอยู่โดเมนเดียวกับ clientId state?: string; // ค่าเริ่มต้นสร้าง CSRF state สุ่ม 16 ไบต์อัตโนมัติ workspaceId?: string; // เลือกพื้นที่ทำงานล่วงหน้า ข้ามหน้าเลือกองค์กร storage?: StorageLike; // ค่าเริ่มต้น sessionStorage redirect?: (url: string) => void; // ค่าเริ่มต้น location.assign navigate?: boolean; // false = แค่สร้าง URL ไม่เปลี่ยนเส้นทาง (popup/ทดสอบ) } interface WorkspaceLoginRequest { url: string; state: string; codeVerifier: string; }
                      
                      interface StartWorkspaceLoginOptions {
  authorizeUrl: string;  // endpoint /authorize ของแพลตฟอร์ม (absolute URL)
  clientId: string;      // app home URL ของแอปส่วนขยาย
  redirectUri: string;   // ที่อยู่ callback, host ต้องอยู่โดเมนเดียวกับ clientId
  state?: string;        // ค่าเริ่มต้นสร้าง CSRF state สุ่ม 16 ไบต์อัตโนมัติ
  workspaceId?: string;  // เลือกพื้นที่ทำงานล่วงหน้า ข้ามหน้าเลือกองค์กร
  storage?: StorageLike; // ค่าเริ่มต้น sessionStorage
  redirect?: (url: string) => void; // ค่าเริ่มต้น location.assign
  navigate?: boolean;    // false = แค่สร้าง URL ไม่เปลี่ยนเส้นทาง (popup/ทดสอบ)
}
interface WorkspaceLoginRequest { url: string; state: string; codeVerifier: string; }

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

readAuthorizeCallback(search?, storage?): AuthorizeCallback | null

ที่หน้า Landing ของ callback: อ่าน code + state, ตรวจสอบ state(CSRF), คืนค่า code + codeVerifier ที่เก็บไว้ จะไม่บริโภคคำขอที่เก็บไว้(การบริโภคถูกเลื่อนไปจนแลกสำเร็จ)เพื่อให้ความล้มเหลวชั่วคราวสามารถรีเฟรชลองใหม่ได้ เมื่อไม่มี code และไม่มี error จะคืนค่า null

interface AuthorizeCallback { code: string; state: string | null; codeVerifier: string; }
                      
                      interface AuthorizeCallback { code: string; state: string | null; codeVerifier: string; }

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

stripAuthorizeCallback(ctx?): void

ลบ code / state ออกจาก URL ปัจจุบัน

completeWorkspaceLogin(options): Promise<WorkspaceIdentity>

ขั้นตอนสะดวกระดับ use: อ่านและตรวจสอบ callback → POST {code, codeVerifier} ให้แบ็กเอนด์ของคุณ → เมื่อสำเร็จลบ URL → คืนค่าตัวตน

interface CompleteWorkspaceLoginOptions { exchangeUrl: string; // อินเทอร์เฟซแลกของแบ็กเอนด์คุณเอง fetch?: typeof fetch; search?: string; // ค่าเริ่มต้น location.search storage?: StorageLike; // ค่าเริ่มต้น sessionStorage strip?: boolean; // ค่าเริ่มต้น true }
                      
                      interface CompleteWorkspaceLoginOptions {
  exchangeUrl: string;   // อินเทอร์เฟซแลกของแบ็กเอนด์คุณเอง
  fetch?: typeof fetch;
  search?: string;       // ค่าเริ่มต้น location.search
  storage?: StorageLike; // ค่าเริ่มต้น sessionStorage
  strip?: boolean;       // ค่าเริ่มต้น true
}

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

ค่าของ WorkspaceLoginError.code

code ความหมาย
NoCallback ไม่มี authorization code ใน URL
MissingRequest ไม่มี/คำขอล็อกอินที่เก็บไว้เสียหาย(เรียก startWorkspaceLogin ก่อน)
StateMismatch การตรวจสอบ CSRF state ไม่ผ่าน
AuthorizeError callback เป็นการเปลี่ยนเส้นทางข้อผิดพลาด OAuth(?error=...
NoFetch ไม่มี implementation ของ fetch ให้ใช้
ExchangeFailed อินเทอร์เฟซแลกคืนค่าไม่ใช่ 2xx
InvalidResponse อินเทอร์เฟซแลกคืนค่า JSON ไม่ถูกต้อง
CryptoUnavailable ไม่มี Web Crypto(ต้องบริบทที่ปลอดภัย HTTPS/localhost)
StorageUnavailable ไม่มี sessionStorage(ไม่สามารถเก็บ PKCE verifier)
InvalidAuthorizeUrl authorizeUrl ไม่ใช่ absolute URL

สาม. ติดตั้งในเครื่องจากไฟล์บีบอัด

unzip workspace-extension-sdk-0.1.0.zip # ทั้งสองแพ็กเกจ pre-build dist มาแล้ว (main=dist/index.js, types=dist/index.d.ts) npm i ./workspace-extension-sdk/packages/verify # แบ็กเอนด์ npm i ./workspace-extension-sdk/packages/browser # ฟรอนต์เอนด์
                      
                      unzip workspace-extension-sdk-0.1.0.zip
# ทั้งสองแพ็กเกจ pre-build dist มาแล้ว (main=dist/index.js, types=dist/index.d.ts)
npm i ./workspace-extension-sdk/packages/verify   # แบ็กเอนด์
npm i ./workspace-extension-sdk/packages/browser  # ฟรอนต์เอนด์

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

หากต้องการ build เอง / รันทดสอบ:

cd workspace-extension-sdk npm install npm run build # tsc → dist ของแต่ละแพ็กเกจ npm test # node --test (ไม่มี dependency ทดสอบภายนอก, ต้อง Node ≥ 22.6 เพื่อรัน .ts ตรง ๆ) npm run type-check # tsc --noEmit
                      
                      cd workspace-extension-sdk
npm install
npm run build       # tsc → dist ของแต่ละแพ็กเกจ
npm test            # node --test (ไม่มี dependency ทดสอบภายนอก, ต้อง Node ≥ 22.6 เพื่อรัน .ts ตรง ๆ)
npm run type-check  # tsc --noEmit

                    
บล็อกโค้ดนี้ในหน้าต่างลอย