cabalspy-id 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -112
- package/LICENSE +21 -21
- package/README.md +505 -475
- package/dist/api.cjs +5 -3
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +16 -14
- package/dist/chunk-A2ZAFAWC.cjs.map +1 -1
- package/dist/chunk-ABFIPCBY.cjs +109 -0
- package/dist/chunk-ABFIPCBY.cjs.map +1 -0
- package/dist/{chunk-Z3UPDOBY.js → chunk-DBYCRBVS.js} +9 -7
- package/dist/chunk-DBYCRBVS.js.map +1 -0
- package/dist/{chunk-P3MDLWLN.cjs → chunk-PKFIMBPW.cjs} +113 -54
- package/dist/chunk-PKFIMBPW.cjs.map +1 -0
- package/dist/{chunk-L4RHSWOA.cjs → chunk-ULEBNY2A.cjs} +121 -212
- package/dist/chunk-ULEBNY2A.cjs.map +1 -0
- package/dist/{chunk-X3OFGGSE.cjs → chunk-VDFCMYLR.cjs} +50 -48
- package/dist/chunk-VDFCMYLR.cjs.map +1 -0
- package/dist/chunk-VOTYIQAY.js +109 -0
- package/dist/chunk-VOTYIQAY.js.map +1 -0
- package/dist/{chunk-3LKLY5WQ.js → chunk-XLBO2RSL.js} +15 -106
- package/dist/chunk-XLBO2RSL.js.map +1 -0
- package/dist/{chunk-3P7QK2PG.js → chunk-Z23B5TY6.js} +89 -30
- package/dist/chunk-Z23B5TY6.js.map +1 -0
- package/dist/index.cjs +13 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +25 -17
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +55 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +28 -5
- package/dist/react.d.ts +28 -5
- package/dist/react.js +55 -2
- package/dist/react.js.map +1 -1
- package/dist/thesis.cjs +11 -3
- package/dist/thesis.cjs.map +1 -1
- package/dist/thesis.d.cts +42 -11
- package/dist/thesis.d.ts +42 -11
- package/dist/thesis.js +24 -16
- package/dist/{users-BDLkwW1p.d.cts → users-3DAw88_U.d.cts} +4 -4
- package/dist/{users-CDlDRuyv.d.ts → users-B4r33i4O.d.ts} +4 -4
- package/dist/widget.cjs +28 -12
- package/dist/widget.cjs.map +1 -1
- package/dist/widget.d.cts +37 -6
- package/dist/widget.d.ts +37 -6
- package/dist/widget.js +17 -1
- package/dist/widget.js.map +1 -1
- package/package.json +147 -147
- package/dist/chunk-3LKLY5WQ.js.map +0 -1
- package/dist/chunk-3P7QK2PG.js.map +0 -1
- package/dist/chunk-L4RHSWOA.cjs.map +0 -1
- package/dist/chunk-P3MDLWLN.cjs.map +0 -1
- package/dist/chunk-X3OFGGSE.cjs.map +0 -1
- package/dist/chunk-Z3UPDOBY.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/thesis.ts"],"sourcesContent":["// ═══════════════════════════════════════════════════════════════════════════\n// PUBLIC THESIS API — https://id.cabalspy.xyz/thesis/v1 (browser friendly)\n// ═══════════════════════════════════════════════════════════════════════════\n//\n// Every call carries your instance's PUBLIC client_id (header X-CSID-Client-Id,\n// and client_id on the websocket URL). Reading needs nothing else. Writing needs\n// the signed-in user's CabalSpy ID access token with the scope \"thesis\" (e.g.\n// from the login widget's csid:login event). No cookies are used, so this runs in\n// the browser as is (from one of the instance's allowed origins). Your API key is\n// never needed here (and must never reach a browser).\n\nimport {\n type ApiErrorBody,\n AuthenticationError,\n BadRequestError,\n CabalSpyIDError,\n ConnectionError,\n EMPTY_RATE_LIMIT,\n InvalidResponseError,\n PermissionError,\n RateLimitError,\n errorFromStatus,\n} from './errors';\nimport {\n CHAINS,\n type Chain,\n type Pagination,\n THESIS_EMOJIS,\n THESIS_REPORT_REASONS,\n type Thesis,\n type ThesisCounts,\n type ThesisEligibility,\n type ThesisEmoji,\n type ThesisNotices,\n type ThesisNotifications,\n type ThesisReactions,\n type ThesisReportReason,\n type ThesisSort,\n type ThesisStance,\n type ThesisSummary,\n type ThesisToken,\n} from './types';\nimport { USER_AGENT } from './util';\n\nexport const DEFAULT_THESIS_URL = 'https://id.cabalspy.xyz/thesis/v1';\nexport const DEFAULT_THESIS_WS_URL = 'wss://id.cabalspy.xyz/thesis/v1/ws';\n/** Header that carries the instance's public client_id on every thesis API call. */\nexport const CLIENT_ID_HEADER = 'X-CSID-Client-Id';\n\nconst CLIENT_ID_REQUIRED_MESSAGE =\n \"ThesisClient needs your instance's public client_id (csid_..., shown in the dashboard next to the API key): \" +\n \"new ThesisClient({ clientId: 'csid_...' }). It is not the API key; never put the API key in a browser.\";\n\n/** Throws an AuthenticationError with code `client_id_required` when the client_id is missing or empty. */\nexport function assertClientId(clientId: unknown): asserts clientId is string {\n if (typeof clientId !== 'string' || !clientId.trim()) {\n throw new AuthenticationError(CLIENT_ID_REQUIRED_MESSAGE, {\n code: 'client_id_required',\n parameter: 'clientId',\n docs: 'https://docs.cabalspy.xyz/cabalspy-id/errors#client_id_required',\n });\n }\n}\n\nexport interface ThesisScopeOptions {\n /**\n * `global` (default): theses written on every platform. `instance`: only the\n * ones written on the instance named by `clientId`.\n */\n scope?: 'global' | 'instance';\n /**\n * Instance whose theses `scope: 'instance'` reads. Defaults to the client's\n * own `clientId`; you rarely need to set it per call.\n */\n clientId?: string;\n}\n\nexport interface ThesisClientOptions {\n /**\n * Required. Your instance's PUBLIC client_id (`csid_...`), shown in the\n * dashboard next to the API key. It is not a secret and not the API key. Sent\n * as `X-CSID-Client-Id` on every call and as `client_id` on the live socket;\n * also used for `scope: 'instance'` and credits posts to your app.\n */\n clientId: string;\n /**\n * `global` (default): theses written on every platform. `instance`: only the\n * ones written on your instance.\n */\n scope?: 'global' | 'instance';\n /** The signed-in user's access token (scope `thesis`). Needed only for writing. */\n accessToken?: string | null;\n /** Default: https://id.cabalspy.xyz/thesis/v1 */\n baseUrl?: string;\n /** Default: wss://id.cabalspy.xyz/thesis/v1/ws */\n wsUrl?: string;\n /** Per-request timeout in ms. Default: 15_000 */\n timeout?: number;\n fetch?: typeof globalThis.fetch;\n}\n\nexport interface TokenThesesPage {\n items: Thesis[];\n pagination: Pagination;\n token: ThesisToken;\n summary: ThesisCounts;\n}\n\nexport interface ThesisPage {\n items: Thesis[];\n pagination: Pagination;\n}\n\nexport interface TokenThesesOptions extends ThesisScopeOptions {\n sort?: ThesisSort;\n stance?: ThesisStance;\n /** 1-50. Default: 20 */\n limit?: number;\n cursor?: string;\n}\n\nexport interface ThesisViewer {\n csid_id: string;\n username: string | null;\n name: string | null;\n image_url: string | null;\n}\n\n/** Eligibility of the signed-in user, who that is, and their unread moderation notices. */\nexport type ThesisMe = ThesisEligibility & { user: ThesisViewer; notices: ThesisNotices };\n\nfunction assertChain(chain: string): void {\n if (!(CHAINS as readonly string[]).includes(chain)) {\n throw new BadRequestError(`Unknown blockchain '${chain}'`, { code: 'invalid_parameter', parameter: 'chain', allowed: [...CHAINS] });\n }\n}\n\nconst enc = encodeURIComponent;\n\n/** Query parameters for a scope; instance scope needs the client_id. */\nfunction scopeQuery(scope: string | undefined, clientId: string | undefined): Record<string, string | undefined> {\n if (scope === undefined || scope === 'global') return scope ? { scope } : {};\n if (scope !== 'instance') {\n throw new BadRequestError(`Invalid value for 'scope': ${scope}`, {\n code: 'invalid_parameter',\n parameter: 'scope',\n allowed: ['global', 'instance'],\n });\n }\n if (!clientId) {\n throw new BadRequestError('scope=instance needs clientId', { code: 'missing_parameter', parameter: 'clientId' });\n }\n return { scope, client_id: clientId };\n}\n\n/**\n * Client for the public thesis API, e.g. in a browser app that builds its own\n * thesis UI.\n *\n * const theses = new ThesisClient({ clientId: 'csid_...', accessToken }); // token from csid:login\n * const page = await theses.list('solana', mint, { sort: 'top' });\n * await theses.post('solana', mint, { body: 'Strong community, early.', stance: 'bull' });\n * const live = theses.live('solana', mint);\n * live.on('thesis', (t) => render(t));\n * await live.connect();\n */\nexport class ThesisClient {\n readonly baseUrl: string;\n readonly wsUrl: string;\n readonly timeout: number;\n readonly scope?: 'global' | 'instance';\n /** Your instance's public client_id, sent on every call. */\n readonly clientId: string;\n private accessToken: string | null;\n private readonly fetchImpl: typeof globalThis.fetch;\n\n constructor(options: ThesisClientOptions) {\n assertClientId(options?.clientId);\n this.accessToken = options.accessToken ?? null;\n this.baseUrl = (options.baseUrl ?? DEFAULT_THESIS_URL).replace(/\\/+$/, '');\n this.wsUrl = (options.wsUrl ?? DEFAULT_THESIS_WS_URL).replace(/\\/+$/, '');\n this.timeout = options.timeout ?? 15_000;\n this.scope = options.scope;\n this.clientId = options.clientId.trim();\n scopeQuery(this.scope, this.clientId);\n this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /** Sets (or with null clears) the user's access token, e.g. after sign-in or sign-out. */\n setAccessToken(token: string | null | undefined): void {\n this.accessToken = token ?? null;\n }\n\n private async call<T>(\n method: 'GET' | 'POST' | 'DELETE',\n path: string,\n opts: { auth?: 'required' | 'optional'; body?: unknown; query?: Record<string, string | number | undefined> } = {},\n ): Promise<{ data: T; extra: Record<string, unknown> }> {\n if (opts.auth === 'required' && !this.accessToken) {\n throw new AuthenticationError('Sign in with CabalSpy ID to write (an access token with the scope \"thesis\").', {\n status: 401,\n code: 'sign_in_required',\n });\n }\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(opts.query ?? {})) if (v !== undefined && v !== '') sp.set(k, String(v));\n const qs = sp.toString();\n const url = `${this.baseUrl}${path}${qs ? `?${qs}` : ''}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n let res: Response;\n let text: string;\n try {\n res = await this.fetchImpl(url, {\n method,\n signal: controller.signal,\n credentials: 'omit',\n headers: {\n Accept: 'application/json',\n [CLIENT_ID_HEADER]: this.clientId,\n ...(typeof window === 'undefined' ? { 'User-Agent': USER_AGENT } : {}),\n ...(this.accessToken && opts.auth ? { Authorization: `Bearer ${this.accessToken}` } : {}),\n ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n },\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n });\n text = await res.text();\n } catch (err) {\n const aborted = (err as Error)?.name === 'AbortError';\n throw new ConnectionError(aborted ? `Request timed out after ${this.timeout} ms: ${method} ${path}` : `Network error: ${(err as Error)?.message}`, {\n code: aborted ? 'timeout' : 'connection_error',\n });\n } finally {\n clearTimeout(timer);\n }\n let parsed: Record<string, unknown> | null = null;\n try {\n parsed = text ? (JSON.parse(text) as Record<string, unknown>) : null;\n } catch {\n parsed = null;\n }\n if (!res.ok || parsed?.success === false) {\n const err = (parsed?.error ?? null) as ApiErrorBody | null;\n const ra = res.headers.get('Retry-After');\n const retryAfter = ra !== null && ra.trim() !== '' && Number.isFinite(Number(ra)) ? Number(ra) : null;\n throw errorFromStatus(res.status, err, { ...EMPTY_RATE_LIMIT }, retryAfter, `HTTP ${res.status} on ${method} ${path}`);\n }\n if (!parsed || !('data' in parsed)) {\n throw new InvalidResponseError(`Unexpected response shape on ${method} ${path}`, { status: res.status, code: 'invalid_response' });\n }\n const { data, success: _s, ...extra } = parsed;\n return { data: data as T, extra };\n }\n\n private tokenPath(chain: Chain, mint: string): string {\n assertChain(chain);\n if (!mint) throw new BadRequestError(\"Parameter 'mint' is required\", { code: 'missing_parameter', parameter: 'mint' });\n return `/tokens/${enc(chain)}/${enc(mint)}`;\n }\n\n // ── reading ───────────────────────────────────────────────────────────────\n\n /** Theses on a token, with the token's info and counts. */\n async list(chain: Chain, mint: string, opts: TokenThesesOptions = {}): Promise<TokenThesesPage> {\n const { data, extra } = await this.call<Thesis[]>('GET', this.tokenPath(chain, mint), {\n auth: 'optional',\n query: {\n sort: opts.sort,\n stance: opts.stance,\n limit: opts.limit,\n cursor: opts.cursor,\n ...scopeQuery(opts.scope ?? this.scope, opts.clientId ?? this.clientId),\n },\n });\n return {\n items: data,\n pagination: extra.pagination as Pagination,\n token: extra.token as ThesisToken,\n summary: extra.summary as ThesisCounts,\n };\n }\n\n /** Every thesis on a token, following `next_cursor`. */\n async *iterate(chain: Chain, mint: string, opts: TokenThesesOptions = {}): AsyncGenerator<Thesis, void, void> {\n let cursor = opts.cursor;\n while (true) {\n const page = await this.list(chain, mint, { ...opts, cursor });\n yield* page.items;\n if (!page.pagination?.has_more || !page.pagination.next_cursor) return;\n cursor = page.pagination.next_cursor;\n }\n }\n\n /** Counts (theses, bull, bear, authors) and token info. */\n async summary(chain: Chain, mint: string, opts: ThesisScopeOptions = {}): Promise<ThesisSummary> {\n const query = scopeQuery(opts.scope ?? this.scope, opts.clientId ?? this.clientId);\n return (await this.call<ThesisSummary>('GET', `${this.tokenPath(chain, mint)}/summary`, { query })).data;\n }\n\n /** One thesis or reply. */\n async get(id: string): Promise<Thesis> {\n return (await this.call<Thesis>('GET', `/items/${enc(id)}`, { auth: 'optional' })).data;\n }\n\n /** Replies to a thesis. */\n async replies(id: string, opts: { limit?: number; cursor?: string } = {}): Promise<ThesisPage> {\n const { data, extra } = await this.call<Thesis[]>('GET', `/items/${enc(id)}/replies`, { auth: 'optional', query: opts });\n return { items: data, pagination: extra.pagination as Pagination };\n }\n\n /** Theses of one user (their csid_id). */\n async byUser(csidId: string, opts: { limit?: number; cursor?: string } = {}): Promise<ThesisPage> {\n const { data, extra } = await this.call<Thesis[]>('GET', `/users/${enc(csidId)}`, { auth: 'optional', query: opts });\n return { items: data, pagination: extra.pagination as Pagination };\n }\n\n // ── the signed-in user ────────────────────────────────────────────────────\n\n /** May the signed-in user post (holder) or reply on this token, and who are they. */\n async me(chain: Chain, mint: string): Promise<ThesisMe> {\n return (await this.call<ThesisMe>('GET', `${this.tokenPath(chain, mint)}/me`, { auth: 'required' })).data;\n }\n\n /** Posts a thesis. Holders only (`not_a_holder` otherwise). Up to 1000 characters. */\n async post(chain: Chain, mint: string, params: { body: string; stance?: ThesisStance }): Promise<Thesis> {\n if (params.stance && params.stance !== 'bull' && params.stance !== 'bear') {\n throw new BadRequestError('stance must be bull or bear', { code: 'invalid_parameter', parameter: 'stance', allowed: ['bull', 'bear'] });\n }\n const body = { ...params, client_id: this.clientId };\n return (await this.call<Thesis>('POST', this.tokenPath(chain, mint), { auth: 'required', body })).data;\n }\n\n /** Replies to a thesis. Any signed-in user may reply. */\n async reply(id: string, body: string): Promise<Thesis> {\n const payload = { body, client_id: this.clientId };\n return (await this.call<Thesis>('POST', `/items/${enc(id)}/replies`, { auth: 'required', body: payload })).data;\n }\n\n /** Adds (or with on=false removes) a reaction. */\n async react(id: string, emoji: ThesisEmoji, on = true): Promise<ThesisReactions> {\n if (!(THESIS_EMOJIS as readonly string[]).includes(emoji)) {\n throw new BadRequestError('Unknown reaction', { code: 'invalid_parameter', parameter: 'emoji', allowed: [...THESIS_EMOJIS] });\n }\n return (await this.call<ThesisReactions>('POST', `/items/${enc(id)}/reactions`, { auth: 'required', body: { emoji, on } })).data;\n }\n\n /** Reports a post (hidden for review after 3 reports). */\n async report(id: string, reason: ThesisReportReason, note?: string): Promise<{ reported: true }> {\n if (!(THESIS_REPORT_REASONS as readonly string[]).includes(reason)) {\n throw new BadRequestError('Choose a reason', { code: 'invalid_parameter', parameter: 'reason', allowed: [...THESIS_REPORT_REASONS] });\n }\n return (await this.call<{ reported: true }>('POST', `/items/${enc(id)}/report`, { auth: 'required', body: { reason, note } })).data;\n }\n\n /** Deletes the signed-in user's own thesis or reply. */\n async delete(id: string): Promise<{ deleted: true }> {\n return (await this.call<{ deleted: true }>('DELETE', `/items/${enc(id)}`, { auth: 'required' })).data;\n }\n\n /** The signed-in user's moderation notifications, with the unread count. */\n async notifications(): Promise<ThesisNotifications> {\n return (await this.call<ThesisNotifications>('GET', '/me/notifications', { auth: 'required' })).data;\n }\n\n /** Marks all of the signed-in user's notifications as read. */\n async markNotificationsRead(): Promise<{ read: true }> {\n return (await this.call<{ read: true }>('POST', '/me/notifications/read', { auth: 'required' })).data;\n }\n\n /** Appeals a moderation decision (an `appealable` notification; 10-1000 characters, within 30 days). */\n async appeal(notificationId: number, message: string): Promise<{ appealed: true }> {\n if (!message) throw new BadRequestError(\"Parameter 'message' is required\", { code: 'missing_parameter', parameter: 'message' });\n return (\n await this.call<{ appealed: true }>('POST', `/me/notifications/${enc(String(notificationId))}/appeal`, {\n auth: 'required',\n body: { message },\n })\n ).data;\n }\n\n // ── live ──────────────────────────────────────────────────────────────────\n\n /**\n * Live updates for one token: thesis, reply, reaction, removed. The socket URL\n * carries `client_id`. With `scope: 'instance'` only theses written on your instance.\n */\n live(chain: Chain, mint: string, options: ThesisLiveOptions & ThesisScopeOptions = {}): ThesisLive {\n this.tokenPath(chain, mint);\n const sp = new URLSearchParams({\n client_id: this.clientId,\n ...(scopeQuery(options.scope ?? this.scope, options.clientId ?? this.clientId) as Record<string, string>),\n });\n return new ThesisLive(`${this.wsUrl}/${enc(chain)}/${enc(mint)}?${sp.toString()}`, options);\n }\n}\n\n// ── live websocket ─────────────────────────────────────────────────────────\n\nexport interface ThesisLiveOptions {\n /** Default: true */\n reconnect?: boolean;\n /** Default: 1000 */\n reconnectDelay?: number;\n /** Default: 30_000 */\n maxReconnectDelay?: number;\n /** The server sends a heartbeat every 30 s; silence this long reopens the socket. 0 disables. Default: 75_000 */\n heartbeatTimeout?: number;\n /** WebSocket implementation for Node < 22 (the `ws` package). */\n WebSocketImpl?: unknown;\n}\n\nexport interface ThesisLiveEventMap {\n thesis: Thesis;\n reply: Thesis;\n reaction: ThesisReactions;\n removed: { id: string };\n heartbeat: Record<string, never>;\n open: undefined;\n close: { code?: number; reason?: string };\n error: CabalSpyIDError;\n}\n\ntype LiveHandler<T> = (payload: T) => void;\n\ninterface MinimalWebSocket {\n close(code?: number, reason?: string): void;\n readyState: number;\n onopen: ((ev: unknown) => void) | null;\n onclose: ((ev: unknown) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n onmessage: ((ev: { data: unknown }) => void) | null;\n}\n\n/** Close codes of the live socket for client_id checks (the close reason is the error code). */\nexport const THESIS_WS_CLOSE_CODES = {\n /** client_id_required or invalid_client: final. */\n CLIENT: 4401,\n /** origin_not_allowed: final. */\n ORIGIN: 4403,\n /** rate_limited: reconnects after 60 s. */\n RATE_LIMITED: 4429,\n} as const;\n\n/** Wait after a 4429 close before reconnecting (the server's Retry-After). */\nconst RATE_LIMIT_BACKOFF_MS = 60_000;\n\nconst docsFor = (code: string) => `https://docs.cabalspy.xyz/cabalspy-id/errors#${code}`;\n\n/** The error for a refusing close code of the live socket, or null for an ordinary close. */\nexport function thesisCloseError(code: number | undefined, reason?: string): CabalSpyIDError | null {\n if (code === 4401) {\n const c = reason === 'invalid_client' ? 'invalid_client' : 'client_id_required';\n const msg =\n c === 'invalid_client'\n ? 'The live socket was refused (4401): unknown or inactive client_id'\n : \"The live socket was refused (4401): your instance's public client_id is required\";\n return new AuthenticationError(msg, { code: c, status: 401, docs: docsFor(c) });\n }\n if (code === 4403) {\n return new PermissionError(\"The live socket was refused (4403): this page's origin is not one of the instance's allowed origins\", {\n code: 'origin_not_allowed',\n status: 403,\n docs: docsFor('origin_not_allowed'),\n });\n }\n if (code === 4429) {\n return new RateLimitError('The live socket was refused (4429): rate limited; reconnecting in 60 s', {\n code: 'rate_limited',\n status: 429,\n docs: docsFor('rate_limited'),\n retryAfter: RATE_LIMIT_BACKOFF_MS / 1000,\n });\n }\n if (code === 1008) {\n return new PermissionError('The live socket was refused (1008): invalid token address or too many sockets from this IP', {\n code: 'ws_refused',\n status: 403,\n });\n }\n return null;\n}\n\n/**\n * Public live socket for one token. Messages arrive as `thesis`, `reply`,\n * `reaction` and `removed`; it reconnects after drops or missed heartbeats.\n * Codes 1008 (invalid token address, too many sockets), 4401 (client_id\n * missing or invalid) and 4403 (origin not allowed) are final; after 4429\n * (rate limited) it waits 60 s before reconnecting.\n */\nexport class ThesisLive {\n private ws: MinimalWebSocket | null = null;\n private readonly handlers = new Map<string, Set<LiveHandler<never>>>();\n private attempt = 0;\n private closedByUser = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private watchdog: ReturnType<typeof setTimeout> | null = null;\n private readonly opts: Required<Omit<ThesisLiveOptions, 'WebSocketImpl'>>;\n\n constructor(\n readonly url: string,\n private readonly options: ThesisLiveOptions = {},\n ) {\n this.opts = {\n reconnect: options.reconnect ?? true,\n reconnectDelay: options.reconnectDelay ?? 1_000,\n maxReconnectDelay: options.maxReconnectDelay ?? 30_000,\n heartbeatTimeout: options.heartbeatTimeout ?? 75_000,\n };\n }\n\n get connected(): boolean {\n return this.ws?.readyState === 1;\n }\n\n on<E extends keyof ThesisLiveEventMap>(event: E, handler: LiveHandler<ThesisLiveEventMap[E]>): this {\n let set = this.handlers.get(event);\n if (!set) this.handlers.set(event, (set = new Set()));\n set.add(handler as LiveHandler<never>);\n return this;\n }\n\n off<E extends keyof ThesisLiveEventMap>(event: E, handler: LiveHandler<ThesisLiveEventMap[E]>): this {\n this.handlers.get(event)?.delete(handler as LiveHandler<never>);\n return this;\n }\n\n private emit<E extends keyof ThesisLiveEventMap>(event: E, payload: ThesisLiveEventMap[E]): void {\n for (const h of [...(this.handlers.get(event) ?? [])]) {\n try {\n (h as LiveHandler<ThesisLiveEventMap[E]>)(payload);\n } catch (err) {\n if (event !== 'error') this.emit('error', new CabalSpyIDError(String((err as Error)?.message ?? err), { code: 'handler_error' }));\n }\n }\n }\n\n /** Opens the socket; resolves once it is open. */\n connect(): Promise<void> {\n this.closedByUser = false;\n const WS = (this.options.WebSocketImpl ?? (globalThis as { WebSocket?: unknown }).WebSocket) as\n | (new (url: string) => MinimalWebSocket)\n | undefined;\n if (!WS) {\n return Promise.reject(\n new ConnectionError('No WebSocket implementation found. On Node < 22, run `npm i ws` and pass { WebSocketImpl: WebSocket }.', {\n code: 'no_websocket',\n }),\n );\n }\n return new Promise<void>((resolve, reject) => {\n let settled = false;\n const ws = new WS(this.url);\n this.ws = ws;\n ws.onopen = () => {\n this.attempt = 0;\n this.arm();\n this.emit('open', undefined);\n if (!settled) {\n settled = true;\n resolve();\n }\n };\n ws.onmessage = (ev) => {\n if (this.ws !== ws) return;\n this.arm();\n let msg: { type?: string; data?: unknown };\n try {\n msg = JSON.parse(typeof ev.data === 'string' ? ev.data : String(ev.data));\n } catch {\n return;\n }\n if (msg.type === 'thesis' || msg.type === 'reply' || msg.type === 'reaction' || msg.type === 'removed') {\n this.emit(msg.type, msg.data as never);\n } else if (msg.type === 'heartbeat') {\n this.emit('heartbeat', {});\n }\n };\n ws.onerror = () => {\n if (this.ws !== ws) return;\n const err = new ConnectionError(`WebSocket error on ${this.url}`, { code: 'ws_error' });\n this.emit('error', err);\n if (!settled) {\n settled = true;\n reject(err);\n }\n };\n ws.onclose = (ev) => {\n if (this.ws !== ws) return;\n this.ws = null;\n this.disarm();\n const e = (ev ?? {}) as { code?: number; reason?: string };\n this.emit('close', { code: e.code, reason: e.reason });\n const refusal = thesisCloseError(e.code, e.reason);\n const rateLimited = e.code === 4429;\n const final = refusal !== null && !rateLimited;\n if (refusal) this.emit('error', refusal);\n if (!settled) {\n settled = true;\n reject(\n refusal ?? new ConnectionError(`Live socket closed before it opened (code ${e.code ?? 'unknown'})`, { code: 'ws_closed_early' }),\n );\n }\n if (!this.closedByUser && !final && this.opts.reconnect) this.scheduleReconnect(rateLimited ? RATE_LIMIT_BACKOFF_MS : 0);\n };\n });\n }\n\n private scheduleReconnect(minDelay = 0): void {\n const delay =\n Math.max(minDelay, Math.min(this.opts.reconnectDelay * 2 ** this.attempt, this.opts.maxReconnectDelay)) + Math.random() * 250;\n this.attempt++;\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n if (!this.closedByUser) this.connect().catch(() => undefined);\n }, delay);\n }\n\n private arm(): void {\n if (!this.opts.heartbeatTimeout) return;\n this.disarm();\n this.watchdog = setTimeout(() => {\n this.emit('error', new ConnectionError('No heartbeat from the live socket; reconnecting', { code: 'heartbeat_timeout' }));\n this.ws?.close(4000, 'heartbeat timeout');\n }, this.opts.heartbeatTimeout);\n }\n\n private disarm(): void {\n if (this.watchdog) clearTimeout(this.watchdog);\n this.watchdog = null;\n }\n\n /** Closes the socket and stops reconnecting. */\n close(): void {\n this.closedByUser = true;\n this.disarm();\n if (this.reconnectTimer) clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n const ws = this.ws;\n this.ws = null;\n ws?.close(1000, 'client closed');\n this.emit('close', { code: 1000, reason: 'client closed' });\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunkVDFCMYLRcjs = require('./chunk-VDFCMYLR.cjs');
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
var _chunkP3MDLWLNcjs = require('./chunk-P3MDLWLN.cjs');
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
|
|
17
|
+
var _chunkPKFIMBPWcjs = require('./chunk-PKFIMBPW.cjs');
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
@@ -35,6 +35,7 @@ var _chunkP3MDLWLNcjs = require('./chunk-P3MDLWLN.cjs');
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
var _chunkULEBNY2Acjs = require('./chunk-ULEBNY2A.cjs');
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
|
|
@@ -45,10 +46,17 @@ var _chunkP3MDLWLNcjs = require('./chunk-P3MDLWLN.cjs');
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
var _chunkABFIPCBYcjs = require('./chunk-ABFIPCBY.cjs');
|
|
49
54
|
|
|
50
55
|
// src/index.ts
|
|
51
|
-
var src_default =
|
|
56
|
+
var src_default = _chunkULEBNY2Acjs.CabalSpyID;
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
52
60
|
|
|
53
61
|
|
|
54
62
|
|
|
@@ -93,5 +101,5 @@ var src_default = _chunkL4RHSWOAcjs.CabalSpyID;
|
|
|
93
101
|
|
|
94
102
|
|
|
95
103
|
|
|
96
|
-
exports.AuthenticationError =
|
|
104
|
+
exports.AuthenticationError = _chunkABFIPCBYcjs.AuthenticationError; exports.BadRequestError = _chunkABFIPCBYcjs.BadRequestError; exports.CHAINS = _chunkULEBNY2Acjs.CHAINS; exports.CLIENT_ID_HEADER = _chunkPKFIMBPWcjs.CLIENT_ID_HEADER; exports.CURRENCY_BY_CHAIN = _chunkULEBNY2Acjs.CURRENCY_BY_CHAIN; exports.CabalSpyID = _chunkULEBNY2Acjs.CabalSpyID; exports.CabalSpyIDAuth = _chunkVDFCMYLRcjs.CabalSpyIDAuth; exports.CabalSpyIDError = _chunkABFIPCBYcjs.CabalSpyIDError; exports.CabalSpyIDStream = _chunkULEBNY2Acjs.CabalSpyIDStream; exports.ConnectionError = _chunkABFIPCBYcjs.ConnectionError; exports.DEFAULT_BASE_URL = _chunkULEBNY2Acjs.DEFAULT_BASE_URL; exports.DEFAULT_ISSUER = _chunkVDFCMYLRcjs.DEFAULT_ISSUER; exports.DEFAULT_STREAM_URL = _chunkULEBNY2Acjs.DEFAULT_STREAM_URL; exports.DEFAULT_THESES_STREAM_URL = _chunkULEBNY2Acjs.DEFAULT_THESES_STREAM_URL; exports.DEFAULT_THESIS_URL = _chunkPKFIMBPWcjs.DEFAULT_THESIS_URL; exports.DEFAULT_THESIS_WS_URL = _chunkPKFIMBPWcjs.DEFAULT_THESIS_WS_URL; exports.InvalidResponseError = _chunkABFIPCBYcjs.InvalidResponseError; exports.JwksCache = _chunkVDFCMYLRcjs.JwksCache; exports.LEADERBOARD_SORTS = _chunkULEBNY2Acjs.LEADERBOARD_SORTS; exports.MAX_THESES_STREAM_TOKENS = _chunkULEBNY2Acjs.MAX_THESES_STREAM_TOKENS; exports.NotFoundError = _chunkABFIPCBYcjs.NotFoundError; exports.OAuthError = _chunkABFIPCBYcjs.OAuthError; exports.PERIODS = _chunkULEBNY2Acjs.PERIODS; exports.PermissionError = _chunkABFIPCBYcjs.PermissionError; exports.PlanUserLimitError = _chunkABFIPCBYcjs.PlanUserLimitError; exports.RateLimitError = _chunkABFIPCBYcjs.RateLimitError; exports.SDK_VERSION = _chunkULEBNY2Acjs.SDK_VERSION; exports.ServerError = _chunkABFIPCBYcjs.ServerError; exports.THESIS_EMOJIS = _chunkULEBNY2Acjs.THESIS_EMOJIS; exports.THESIS_MAX_BODY = _chunkULEBNY2Acjs.THESIS_MAX_BODY; exports.THESIS_REPORT_REASONS = _chunkULEBNY2Acjs.THESIS_REPORT_REASONS; exports.THESIS_WS_CLOSE_CODES = _chunkPKFIMBPWcjs.THESIS_WS_CLOSE_CODES; exports.ThesesStream = _chunkULEBNY2Acjs.ThesesStream; exports.ThesisClient = _chunkPKFIMBPWcjs.ThesisClient; exports.ThesisLive = _chunkPKFIMBPWcjs.ThesisLive; exports.TokenVerificationError = _chunkABFIPCBYcjs.TokenVerificationError; exports.WebhookVerificationError = _chunkABFIPCBYcjs.WebhookVerificationError; exports.decodeJwt = _chunkVDFCMYLRcjs.decodeJwt; exports.default = src_default; exports.generatePkce = _chunkVDFCMYLRcjs.generatePkce; exports.handleKind = _chunkULEBNY2Acjs.handleKind; exports.hasX = _chunkULEBNY2Acjs.hasX; exports.isLimited = _chunkVDFCMYLRcjs.isLimited; exports.signWebhook = _chunkULEBNY2Acjs.signWebhook; exports.thesisCloseError = _chunkPKFIMBPWcjs.thesisCloseError; exports.verifyWebhook = _chunkULEBNY2Acjs.verifyWebhook;
|
|
97
105
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/cabalspy-id-js/cabalspy-id-js/dist/index.cjs","../src/index.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;AC6EA,IAAO,YAAA,EAAQ,4BAAA;AD3Ef;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,qrFAAC","file":"/home/runner/work/cabalspy-id-js/cabalspy-id-js/dist/index.cjs","sourcesContent":[null,"/**\n * CabalSpy ID SDK — the verified social layer for crypto trading.\n *\n * - CabalSpyID: Data API client (https://api-id.cabalspy.xyz/v1) and live stream\n * - CabalSpyIDAuth: \"Login with CabalSpy ID\" (OpenID Connect, PKCE, ID token verification)\n * - ThesisClient: public token thesis API for browsers (user access token) and live()\n * - verifyWebhook: Standard Webhooks signature verification\n * - cabalspy-id/react: <CabalSpyLogin />, <CabalSpyTheses /> and useCabalSpyID()\n *\n * Focused entry points: cabalspy-id/api, cabalspy-id/thesis, cabalspy-id/widget (browser).\n *\n * Docs: https://docs.cabalspy.xyz/cabalspy-id\n */\n\nexport {\n CabalSpyID,\n DEFAULT_BASE_URL,\n DEFAULT_STREAM_URL,\n DEFAULT_THESES_STREAM_URL,\n type CabalSpyIDOptions,\n type Envelope,\n type Query,\n type ScopedParams,\n type UsersParams,\n type ProfileParams,\n type WalletParams,\n type TrackerParams,\n type HistoryParams,\n type LeaderboardParams,\n type LatestTransactionsParams,\n type ThesisTokenParams,\n type ThesisScopeParam,\n type ListThesesParams,\n type ThesisRepliesParams,\n type LatestThesesParams,\n type UserThesesParams,\n type ThesisWriter,\n type PostThesisParams,\n type ReplyToThesisParams,\n type ReactToThesisParams,\n type ReportThesisParams,\n type DeleteThesisParams,\n} from './client';\n\nexport {\n CabalSpyIDStream,\n type StreamOptions,\n type StreamEvent,\n type StreamEventMap,\n type SubscribedEvent,\n type HeartbeatEvent,\n type CloseEvent,\n type ReconnectEvent,\n type StreamTopic,\n type ThesisStreamEvent,\n} from './stream';\n\nexport {\n CabalSpyIDAuth,\n DEFAULT_ISSUER,\n generatePkce,\n isLimited,\n type CabalSpyIDAuthOptions,\n type TokenEndpointAuthMethod,\n type PkcePair,\n type AuthorizationUrlParams,\n type AuthorizationRequest,\n type CallbackChecks,\n type VerifyIdTokenOptions,\n type LoginResult,\n} from './oidc';\n\nexport {\n ThesesStream,\n MAX_THESES_STREAM_TOKENS,\n type ThesisTokenRef,\n type ThesesStreamOptions,\n type ThesesReadyEvent,\n type ThesesSubscribedEvent,\n type ThesesStreamEventMap,\n} from './thesesStream';\n\nexport {\n ThesisClient,\n ThesisLive,\n DEFAULT_THESIS_URL,\n DEFAULT_THESIS_WS_URL,\n CLIENT_ID_HEADER,\n THESIS_WS_CLOSE_CODES,\n thesisCloseError,\n type ThesisClientOptions,\n type ThesisScopeOptions,\n type TokenThesesPage,\n type ThesisPage,\n type TokenThesesOptions,\n type ThesisViewer,\n type ThesisMe,\n type ThesisLiveOptions,\n type ThesisLiveEventMap,\n} from './thesis';\n\nexport { decodeJwt, JwksCache, type EcJwk, type Jwks, type JwtHeader, type DecodedJwt } from './jwt';\n\nexport { verifyWebhook, signWebhook, type VerifyWebhookOptions, type HeaderSource } from './webhooks';\n\nexport {\n CabalSpyIDError,\n BadRequestError,\n AuthenticationError,\n PermissionError,\n PlanUserLimitError,\n NotFoundError,\n RateLimitError,\n ServerError,\n ConnectionError,\n InvalidResponseError,\n OAuthError,\n TokenVerificationError,\n WebhookVerificationError,\n type ApiErrorBody,\n type RateLimitInfo,\n type CabalSpyIDErrorOptions,\n} from './errors';\n\nexport * from './types';\n\nexport { hasX, handleKind, type UserLike, type HandleKind } from './users';\n\nexport { SDK_VERSION } from './util';\n\nimport { CabalSpyID } from './client';\nexport default CabalSpyID;\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as CabalSpyID } from './users-
|
|
2
|
-
export { A as ApiErrorBody, a as AuthenticationError, B as BadRequestError, b as CabalSpyIDError, c as CabalSpyIDErrorOptions, d as CabalSpyIDOptions, e as CabalSpyIDStream, f as CloseEvent, g as ConnectionError, D as DEFAULT_BASE_URL, h as DEFAULT_STREAM_URL, i as DEFAULT_THESES_STREAM_URL, j as DeleteThesisParams, E as Envelope, H as HandleKind, k as HeaderSource, l as HeartbeatEvent, m as HistoryParams, I as InvalidResponseError, L as LatestThesesParams, n as LatestTransactionsParams, o as LeaderboardParams, p as ListThesesParams, M as MAX_THESES_STREAM_TOKENS, N as NotFoundError, O as OAuthError, P as PermissionError, q as PlanUserLimitError, r as PostThesisParams, s as ProfileParams, Q as Query, R as RateLimitError, t as RateLimitInfo, u as ReactToThesisParams, v as ReconnectEvent, w as ReplyToThesisParams, x as ReportThesisParams, S as SDK_VERSION, y as ScopedParams, z as ServerError, F as StreamEvent, G as StreamEventMap, J as StreamOptions, K as StreamTopic, T as SubscribedEvent, U as ThesesReadyEvent, V as ThesesStream, W as ThesesStreamEventMap, X as ThesesStreamOptions, Y as ThesesSubscribedEvent, Z as ThesisRepliesParams, _ as ThesisScopeParam, $ as ThesisStreamEvent, a0 as ThesisTokenParams, a1 as ThesisTokenRef, a2 as ThesisWriter, a3 as TokenVerificationError, a4 as TrackerParams, a5 as UserLike, a6 as UserThesesParams, a7 as UsersParams, a8 as VerifyWebhookOptions, a9 as WalletParams, aa as WebhookVerificationError, ab as handleKind, ac as hasX, ad as signWebhook, ae as verifyWebhook } from './users-
|
|
1
|
+
import { C as CabalSpyID } from './users-3DAw88_U.cjs';
|
|
2
|
+
export { A as ApiErrorBody, a as AuthenticationError, B as BadRequestError, b as CabalSpyIDError, c as CabalSpyIDErrorOptions, d as CabalSpyIDOptions, e as CabalSpyIDStream, f as CloseEvent, g as ConnectionError, D as DEFAULT_BASE_URL, h as DEFAULT_STREAM_URL, i as DEFAULT_THESES_STREAM_URL, j as DeleteThesisParams, E as Envelope, H as HandleKind, k as HeaderSource, l as HeartbeatEvent, m as HistoryParams, I as InvalidResponseError, L as LatestThesesParams, n as LatestTransactionsParams, o as LeaderboardParams, p as ListThesesParams, M as MAX_THESES_STREAM_TOKENS, N as NotFoundError, O as OAuthError, P as PermissionError, q as PlanUserLimitError, r as PostThesisParams, s as ProfileParams, Q as Query, R as RateLimitError, t as RateLimitInfo, u as ReactToThesisParams, v as ReconnectEvent, w as ReplyToThesisParams, x as ReportThesisParams, S as SDK_VERSION, y as ScopedParams, z as ServerError, F as StreamEvent, G as StreamEventMap, J as StreamOptions, K as StreamTopic, T as SubscribedEvent, U as ThesesReadyEvent, V as ThesesStream, W as ThesesStreamEventMap, X as ThesesStreamOptions, Y as ThesesSubscribedEvent, Z as ThesisRepliesParams, _ as ThesisScopeParam, $ as ThesisStreamEvent, a0 as ThesisTokenParams, a1 as ThesisTokenRef, a2 as ThesisWriter, a3 as TokenVerificationError, a4 as TrackerParams, a5 as UserLike, a6 as UserThesesParams, a7 as UsersParams, a8 as VerifyWebhookOptions, a9 as WalletParams, aa as WebhookVerificationError, ab as handleKind, ac as hasX, ad as signWebhook, ae as verifyWebhook } from './users-3DAw88_U.cjs';
|
|
3
3
|
export { AuthorizationRequest, AuthorizationUrlParams, CabalSpyIDAuth, CabalSpyIDAuthOptions, CallbackChecks, DEFAULT_ISSUER, DecodedJwt, EcJwk, Jwks, JwksCache, JwtHeader, LoginResult, PkcePair, TokenEndpointAuthMethod, VerifyIdTokenOptions, decodeJwt, generatePkce, isLimited } from './api.cjs';
|
|
4
|
-
export { DEFAULT_THESIS_URL, DEFAULT_THESIS_WS_URL, ThesisClient, ThesisClientOptions, ThesisLive, ThesisLiveEventMap, ThesisLiveOptions, ThesisMe, ThesisPage, ThesisScopeOptions, ThesisViewer, TokenThesesOptions, TokenThesesPage } from './thesis.cjs';
|
|
4
|
+
export { CLIENT_ID_HEADER, DEFAULT_THESIS_URL, DEFAULT_THESIS_WS_URL, THESIS_WS_CLOSE_CODES, ThesisClient, ThesisClientOptions, ThesisLive, ThesisLiveEventMap, ThesisLiveOptions, ThesisMe, ThesisPage, ThesisScopeOptions, ThesisViewer, TokenThesesOptions, TokenThesesPage, thesisCloseError } from './thesis.cjs';
|
|
5
5
|
export { A as ActiveTokens, a as ApiResponse, C as CHAINS, b as CURRENCY_BY_CHAIN, c as CabalSpyIDClaims, d as Chain, e as Currency, D as DiscoveryDocument, H as HealthResponse, f as HistoryResponse, g as HistoryToken, h as HistoryTrade, i as HoldingToken, j as HoldingsResponse, L as LEADERBOARD_SORTS, k as LeaderboardEntry, l as LeaderboardResponse, m as LeaderboardSort, n as LifetimeStats, M as MeResponse, O as OidcScope, P as PERIODS, o as Pagination, p as Period, q as PeriodStats, r as PnlCalendarDay, s as PnlCalendarMonth, t as PnlCalendarResponse, u as PnlChartPoint, v as Profile, R as ResponseMeta, S as Scope, T as THESIS_EMOJIS, w as THESIS_MAX_BODY, x as THESIS_REPORT_REASONS, y as Thesis, z as ThesisAuthor, B as ThesisCounts, E as ThesisCreatedData, F as ThesisEligibility, G as ThesisEmoji, I as ThesisLiveItem, J as ThesisNotices, K as ThesisNotification, N as ThesisNotificationKind, Q as ThesisNotifications, U as ThesisPnl, V as ThesisPosition, W as ThesisPositionPrivate, X as ThesisPositionPublic, Y as ThesisReactionData, Z as ThesisReactionUpdate, _ as ThesisReactions, $ as ThesisReactor, a0 as ThesisRemoved, a1 as ThesisReplyData, a2 as ThesisReportReason, a3 as ThesisSort, a4 as ThesisStance, a5 as ThesisSummary, a6 as ThesisToken, a7 as TokenOverview, a8 as TokenResponse, a9 as TrackerResponse, aa as TrackerTrade, ab as Trade, ac as TradeOwner, ad as TraderProfile, ae as VerifiedWallet, af as WalletClaim, ag as WalletLookupResponse, ah as WebhookEvent, ai as WebhookEventType, aj as WinRateBuckets, ak as WinRateDistribution, al as XProfileClaim } from './types-UZeY9n1e.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as CabalSpyID } from './users-
|
|
2
|
-
export { A as ApiErrorBody, a as AuthenticationError, B as BadRequestError, b as CabalSpyIDError, c as CabalSpyIDErrorOptions, d as CabalSpyIDOptions, e as CabalSpyIDStream, f as CloseEvent, g as ConnectionError, D as DEFAULT_BASE_URL, h as DEFAULT_STREAM_URL, i as DEFAULT_THESES_STREAM_URL, j as DeleteThesisParams, E as Envelope, H as HandleKind, k as HeaderSource, l as HeartbeatEvent, m as HistoryParams, I as InvalidResponseError, L as LatestThesesParams, n as LatestTransactionsParams, o as LeaderboardParams, p as ListThesesParams, M as MAX_THESES_STREAM_TOKENS, N as NotFoundError, O as OAuthError, P as PermissionError, q as PlanUserLimitError, r as PostThesisParams, s as ProfileParams, Q as Query, R as RateLimitError, t as RateLimitInfo, u as ReactToThesisParams, v as ReconnectEvent, w as ReplyToThesisParams, x as ReportThesisParams, S as SDK_VERSION, y as ScopedParams, z as ServerError, F as StreamEvent, G as StreamEventMap, J as StreamOptions, K as StreamTopic, T as SubscribedEvent, U as ThesesReadyEvent, V as ThesesStream, W as ThesesStreamEventMap, X as ThesesStreamOptions, Y as ThesesSubscribedEvent, Z as ThesisRepliesParams, _ as ThesisScopeParam, $ as ThesisStreamEvent, a0 as ThesisTokenParams, a1 as ThesisTokenRef, a2 as ThesisWriter, a3 as TokenVerificationError, a4 as TrackerParams, a5 as UserLike, a6 as UserThesesParams, a7 as UsersParams, a8 as VerifyWebhookOptions, a9 as WalletParams, aa as WebhookVerificationError, ab as handleKind, ac as hasX, ad as signWebhook, ae as verifyWebhook } from './users-
|
|
1
|
+
import { C as CabalSpyID } from './users-B4r33i4O.js';
|
|
2
|
+
export { A as ApiErrorBody, a as AuthenticationError, B as BadRequestError, b as CabalSpyIDError, c as CabalSpyIDErrorOptions, d as CabalSpyIDOptions, e as CabalSpyIDStream, f as CloseEvent, g as ConnectionError, D as DEFAULT_BASE_URL, h as DEFAULT_STREAM_URL, i as DEFAULT_THESES_STREAM_URL, j as DeleteThesisParams, E as Envelope, H as HandleKind, k as HeaderSource, l as HeartbeatEvent, m as HistoryParams, I as InvalidResponseError, L as LatestThesesParams, n as LatestTransactionsParams, o as LeaderboardParams, p as ListThesesParams, M as MAX_THESES_STREAM_TOKENS, N as NotFoundError, O as OAuthError, P as PermissionError, q as PlanUserLimitError, r as PostThesisParams, s as ProfileParams, Q as Query, R as RateLimitError, t as RateLimitInfo, u as ReactToThesisParams, v as ReconnectEvent, w as ReplyToThesisParams, x as ReportThesisParams, S as SDK_VERSION, y as ScopedParams, z as ServerError, F as StreamEvent, G as StreamEventMap, J as StreamOptions, K as StreamTopic, T as SubscribedEvent, U as ThesesReadyEvent, V as ThesesStream, W as ThesesStreamEventMap, X as ThesesStreamOptions, Y as ThesesSubscribedEvent, Z as ThesisRepliesParams, _ as ThesisScopeParam, $ as ThesisStreamEvent, a0 as ThesisTokenParams, a1 as ThesisTokenRef, a2 as ThesisWriter, a3 as TokenVerificationError, a4 as TrackerParams, a5 as UserLike, a6 as UserThesesParams, a7 as UsersParams, a8 as VerifyWebhookOptions, a9 as WalletParams, aa as WebhookVerificationError, ab as handleKind, ac as hasX, ad as signWebhook, ae as verifyWebhook } from './users-B4r33i4O.js';
|
|
3
3
|
export { AuthorizationRequest, AuthorizationUrlParams, CabalSpyIDAuth, CabalSpyIDAuthOptions, CallbackChecks, DEFAULT_ISSUER, DecodedJwt, EcJwk, Jwks, JwksCache, JwtHeader, LoginResult, PkcePair, TokenEndpointAuthMethod, VerifyIdTokenOptions, decodeJwt, generatePkce, isLimited } from './api.js';
|
|
4
|
-
export { DEFAULT_THESIS_URL, DEFAULT_THESIS_WS_URL, ThesisClient, ThesisClientOptions, ThesisLive, ThesisLiveEventMap, ThesisLiveOptions, ThesisMe, ThesisPage, ThesisScopeOptions, ThesisViewer, TokenThesesOptions, TokenThesesPage } from './thesis.js';
|
|
4
|
+
export { CLIENT_ID_HEADER, DEFAULT_THESIS_URL, DEFAULT_THESIS_WS_URL, THESIS_WS_CLOSE_CODES, ThesisClient, ThesisClientOptions, ThesisLive, ThesisLiveEventMap, ThesisLiveOptions, ThesisMe, ThesisPage, ThesisScopeOptions, ThesisViewer, TokenThesesOptions, TokenThesesPage, thesisCloseError } from './thesis.js';
|
|
5
5
|
export { A as ActiveTokens, a as ApiResponse, C as CHAINS, b as CURRENCY_BY_CHAIN, c as CabalSpyIDClaims, d as Chain, e as Currency, D as DiscoveryDocument, H as HealthResponse, f as HistoryResponse, g as HistoryToken, h as HistoryTrade, i as HoldingToken, j as HoldingsResponse, L as LEADERBOARD_SORTS, k as LeaderboardEntry, l as LeaderboardResponse, m as LeaderboardSort, n as LifetimeStats, M as MeResponse, O as OidcScope, P as PERIODS, o as Pagination, p as Period, q as PeriodStats, r as PnlCalendarDay, s as PnlCalendarMonth, t as PnlCalendarResponse, u as PnlChartPoint, v as Profile, R as ResponseMeta, S as Scope, T as THESIS_EMOJIS, w as THESIS_MAX_BODY, x as THESIS_REPORT_REASONS, y as Thesis, z as ThesisAuthor, B as ThesisCounts, E as ThesisCreatedData, F as ThesisEligibility, G as ThesisEmoji, I as ThesisLiveItem, J as ThesisNotices, K as ThesisNotification, N as ThesisNotificationKind, Q as ThesisNotifications, U as ThesisPnl, V as ThesisPosition, W as ThesisPositionPrivate, X as ThesisPositionPublic, Y as ThesisReactionData, Z as ThesisReactionUpdate, _ as ThesisReactions, $ as ThesisReactor, a0 as ThesisRemoved, a1 as ThesisReplyData, a2 as ThesisReportReason, a3 as ThesisSort, a4 as ThesisStance, a5 as ThesisSummary, a6 as ThesisToken, a7 as TokenOverview, a8 as TokenResponse, a9 as TrackerResponse, aa as TrackerTrade, ab as Trade, ac as TradeOwner, ad as TraderProfile, ae as VerifiedWallet, af as WalletClaim, ag as WalletLookupResponse, ah as WebhookEvent, ai as WebhookEventType, aj as WinRateBuckets, ak as WinRateDistribution, al as XProfileClaim } from './types-UZeY9n1e.js';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/index.js
CHANGED
|
@@ -5,47 +5,52 @@ import {
|
|
|
5
5
|
decodeJwt,
|
|
6
6
|
generatePkce,
|
|
7
7
|
isLimited
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DBYCRBVS.js";
|
|
9
9
|
import {
|
|
10
|
+
CLIENT_ID_HEADER,
|
|
10
11
|
DEFAULT_THESIS_URL,
|
|
11
12
|
DEFAULT_THESIS_WS_URL,
|
|
13
|
+
THESIS_WS_CLOSE_CODES,
|
|
12
14
|
ThesisClient,
|
|
13
|
-
ThesisLive
|
|
14
|
-
|
|
15
|
+
ThesisLive,
|
|
16
|
+
thesisCloseError
|
|
17
|
+
} from "./chunk-Z23B5TY6.js";
|
|
15
18
|
import {
|
|
16
|
-
AuthenticationError,
|
|
17
|
-
BadRequestError,
|
|
18
19
|
CHAINS,
|
|
19
20
|
CURRENCY_BY_CHAIN,
|
|
20
21
|
CabalSpyID,
|
|
21
|
-
CabalSpyIDError,
|
|
22
22
|
CabalSpyIDStream,
|
|
23
|
-
ConnectionError,
|
|
24
23
|
DEFAULT_BASE_URL,
|
|
25
24
|
DEFAULT_STREAM_URL,
|
|
26
25
|
DEFAULT_THESES_STREAM_URL,
|
|
27
|
-
InvalidResponseError,
|
|
28
26
|
LEADERBOARD_SORTS,
|
|
29
27
|
MAX_THESES_STREAM_TOKENS,
|
|
30
|
-
NotFoundError,
|
|
31
|
-
OAuthError,
|
|
32
28
|
PERIODS,
|
|
33
|
-
PermissionError,
|
|
34
|
-
PlanUserLimitError,
|
|
35
|
-
RateLimitError,
|
|
36
29
|
SDK_VERSION,
|
|
37
|
-
ServerError,
|
|
38
30
|
THESIS_EMOJIS,
|
|
39
31
|
THESIS_MAX_BODY,
|
|
40
32
|
THESIS_REPORT_REASONS,
|
|
41
33
|
ThesesStream,
|
|
42
|
-
TokenVerificationError,
|
|
43
|
-
WebhookVerificationError,
|
|
44
34
|
handleKind,
|
|
45
35
|
hasX,
|
|
46
36
|
signWebhook,
|
|
47
37
|
verifyWebhook
|
|
48
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-XLBO2RSL.js";
|
|
39
|
+
import {
|
|
40
|
+
AuthenticationError,
|
|
41
|
+
BadRequestError,
|
|
42
|
+
CabalSpyIDError,
|
|
43
|
+
ConnectionError,
|
|
44
|
+
InvalidResponseError,
|
|
45
|
+
NotFoundError,
|
|
46
|
+
OAuthError,
|
|
47
|
+
PermissionError,
|
|
48
|
+
PlanUserLimitError,
|
|
49
|
+
RateLimitError,
|
|
50
|
+
ServerError,
|
|
51
|
+
TokenVerificationError,
|
|
52
|
+
WebhookVerificationError
|
|
53
|
+
} from "./chunk-VOTYIQAY.js";
|
|
49
54
|
|
|
50
55
|
// src/index.ts
|
|
51
56
|
var src_default = CabalSpyID;
|
|
@@ -53,6 +58,7 @@ export {
|
|
|
53
58
|
AuthenticationError,
|
|
54
59
|
BadRequestError,
|
|
55
60
|
CHAINS,
|
|
61
|
+
CLIENT_ID_HEADER,
|
|
56
62
|
CURRENCY_BY_CHAIN,
|
|
57
63
|
CabalSpyID,
|
|
58
64
|
CabalSpyIDAuth,
|
|
@@ -80,6 +86,7 @@ export {
|
|
|
80
86
|
THESIS_EMOJIS,
|
|
81
87
|
THESIS_MAX_BODY,
|
|
82
88
|
THESIS_REPORT_REASONS,
|
|
89
|
+
THESIS_WS_CLOSE_CODES,
|
|
83
90
|
ThesesStream,
|
|
84
91
|
ThesisClient,
|
|
85
92
|
ThesisLive,
|
|
@@ -92,6 +99,7 @@ export {
|
|
|
92
99
|
hasX,
|
|
93
100
|
isLimited,
|
|
94
101
|
signWebhook,
|
|
102
|
+
thesisCloseError,
|
|
95
103
|
verifyWebhook
|
|
96
104
|
};
|
|
97
105
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * CabalSpy ID SDK — the verified social layer for crypto trading.\n *\n * - CabalSpyID: Data API client (https://api-id.cabalspy.xyz/v1) and live stream\n * - CabalSpyIDAuth: \"Login with CabalSpy ID\" (OpenID Connect, PKCE, ID token verification)\n * - ThesisClient: public token thesis API for browsers (user access token) and live()\n * - verifyWebhook: Standard Webhooks signature verification\n * - cabalspy-id/react: <CabalSpyLogin />, <CabalSpyTheses /> and useCabalSpyID()\n *\n * Focused entry points: cabalspy-id/api, cabalspy-id/thesis, cabalspy-id/widget (browser).\n *\n * Docs: https://docs.cabalspy.xyz/cabalspy-id\n */\n\nexport {\n CabalSpyID,\n DEFAULT_BASE_URL,\n DEFAULT_STREAM_URL,\n DEFAULT_THESES_STREAM_URL,\n type CabalSpyIDOptions,\n type Envelope,\n type Query,\n type ScopedParams,\n type UsersParams,\n type ProfileParams,\n type WalletParams,\n type TrackerParams,\n type HistoryParams,\n type LeaderboardParams,\n type LatestTransactionsParams,\n type ThesisTokenParams,\n type ThesisScopeParam,\n type ListThesesParams,\n type ThesisRepliesParams,\n type LatestThesesParams,\n type UserThesesParams,\n type ThesisWriter,\n type PostThesisParams,\n type ReplyToThesisParams,\n type ReactToThesisParams,\n type ReportThesisParams,\n type DeleteThesisParams,\n} from './client';\n\nexport {\n CabalSpyIDStream,\n type StreamOptions,\n type StreamEvent,\n type StreamEventMap,\n type SubscribedEvent,\n type HeartbeatEvent,\n type CloseEvent,\n type ReconnectEvent,\n type StreamTopic,\n type ThesisStreamEvent,\n} from './stream';\n\nexport {\n CabalSpyIDAuth,\n DEFAULT_ISSUER,\n generatePkce,\n isLimited,\n type CabalSpyIDAuthOptions,\n type TokenEndpointAuthMethod,\n type PkcePair,\n type AuthorizationUrlParams,\n type AuthorizationRequest,\n type CallbackChecks,\n type VerifyIdTokenOptions,\n type LoginResult,\n} from './oidc';\n\nexport {\n ThesesStream,\n MAX_THESES_STREAM_TOKENS,\n type ThesisTokenRef,\n type ThesesStreamOptions,\n type ThesesReadyEvent,\n type ThesesSubscribedEvent,\n type ThesesStreamEventMap,\n} from './thesesStream';\n\nexport {\n ThesisClient,\n ThesisLive,\n DEFAULT_THESIS_URL,\n DEFAULT_THESIS_WS_URL,\n CLIENT_ID_HEADER,\n THESIS_WS_CLOSE_CODES,\n thesisCloseError,\n type ThesisClientOptions,\n type ThesisScopeOptions,\n type TokenThesesPage,\n type ThesisPage,\n type TokenThesesOptions,\n type ThesisViewer,\n type ThesisMe,\n type ThesisLiveOptions,\n type ThesisLiveEventMap,\n} from './thesis';\n\nexport { decodeJwt, JwksCache, type EcJwk, type Jwks, type JwtHeader, type DecodedJwt } from './jwt';\n\nexport { verifyWebhook, signWebhook, type VerifyWebhookOptions, type HeaderSource } from './webhooks';\n\nexport {\n CabalSpyIDError,\n BadRequestError,\n AuthenticationError,\n PermissionError,\n PlanUserLimitError,\n NotFoundError,\n RateLimitError,\n ServerError,\n ConnectionError,\n InvalidResponseError,\n OAuthError,\n TokenVerificationError,\n WebhookVerificationError,\n type ApiErrorBody,\n type RateLimitInfo,\n type CabalSpyIDErrorOptions,\n} from './errors';\n\nexport * from './types';\n\nexport { hasX, handleKind, type UserLike, type HandleKind } from './users';\n\nexport { SDK_VERSION } from './util';\n\nimport { CabalSpyID } from './client';\nexport default CabalSpyID;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmIA,IAAO,cAAQ;","names":[]}
|
package/dist/react.cjs
CHANGED
|
@@ -30,6 +30,35 @@ __export(react_exports, {
|
|
|
30
30
|
module.exports = __toCommonJS(react_exports);
|
|
31
31
|
var import_react = require("react");
|
|
32
32
|
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var EMPTY_RATE_LIMIT = Object.freeze({ limit: null, remaining: null, reset: null });
|
|
35
|
+
var CabalSpyIDError = class extends Error {
|
|
36
|
+
status;
|
|
37
|
+
code;
|
|
38
|
+
requestId;
|
|
39
|
+
docs;
|
|
40
|
+
parameter;
|
|
41
|
+
allowed;
|
|
42
|
+
rateLimit;
|
|
43
|
+
/** The parsed response body, when there was one. */
|
|
44
|
+
body;
|
|
45
|
+
constructor(message, opts = {}) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = new.target.name;
|
|
48
|
+
this.status = opts.status ?? 0;
|
|
49
|
+
this.code = opts.code ?? "unknown_error";
|
|
50
|
+
this.requestId = opts.requestId;
|
|
51
|
+
this.docs = opts.docs;
|
|
52
|
+
this.parameter = opts.parameter;
|
|
53
|
+
this.allowed = opts.allowed;
|
|
54
|
+
this.rateLimit = opts.rateLimit ?? { ...EMPTY_RATE_LIMIT };
|
|
55
|
+
this.body = opts.body;
|
|
56
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var AuthenticationError = class extends CabalSpyIDError {
|
|
60
|
+
};
|
|
61
|
+
|
|
33
62
|
// src/widget.ts
|
|
34
63
|
var WIDGET_SRC = "https://id.cabalspy.xyz/widget.js";
|
|
35
64
|
var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
@@ -98,11 +127,17 @@ function loginButtonAttributes(o) {
|
|
|
98
127
|
};
|
|
99
128
|
}
|
|
100
129
|
function thesisWidgetAttributes(o) {
|
|
130
|
+
if (typeof o?.clientId !== "string" || !o.clientId.trim()) {
|
|
131
|
+
throw new AuthenticationError(
|
|
132
|
+
"The theses widget needs your instance's public client_id (csid_..., shown in the dashboard next to the API key): { clientId: 'csid_...' }. It is not the API key; never put the API key in a browser.",
|
|
133
|
+
{ code: "client_id_required", parameter: "clientId", docs: "https://docs.cabalspy.xyz/cabalspy-id/errors#client_id_required" }
|
|
134
|
+
);
|
|
135
|
+
}
|
|
101
136
|
return {
|
|
102
137
|
"data-csid-thesis": "",
|
|
103
138
|
"data-chain": o.chain,
|
|
104
139
|
"data-mint": o.mint,
|
|
105
|
-
"data-client-id": o.clientId,
|
|
140
|
+
"data-client-id": o.clientId.trim(),
|
|
106
141
|
"data-theme": o.theme,
|
|
107
142
|
"data-scope": o.scope,
|
|
108
143
|
"data-sort": o.sort,
|
|
@@ -146,6 +181,11 @@ function onWidgetEvents(handlers, clientId) {
|
|
|
146
181
|
window.removeEventListener("csid:error", error);
|
|
147
182
|
};
|
|
148
183
|
}
|
|
184
|
+
async function setThesisSignInHandler(handler, scriptSrc) {
|
|
185
|
+
if (!isBrowser()) return;
|
|
186
|
+
const csid = await loadCabalSpyIDWidget(scriptSrc);
|
|
187
|
+
csid.thesis?.setSignInHandler?.(handler);
|
|
188
|
+
}
|
|
149
189
|
|
|
150
190
|
// src/react.tsx
|
|
151
191
|
function CabalSpyLogin(props) {
|
|
@@ -181,9 +221,22 @@ function CabalSpyLogin(props) {
|
|
|
181
221
|
return (0, import_react.createElement)("div", { ...attrs, id: props.id, className: props.className, style: props.style });
|
|
182
222
|
}
|
|
183
223
|
function CabalSpyTheses(props) {
|
|
184
|
-
const { accessToken, scriptSrc } = props;
|
|
224
|
+
const { accessToken, scriptSrc, onSignIn } = props;
|
|
185
225
|
const attrs = thesisWidgetAttributes(props);
|
|
186
226
|
const key = JSON.stringify(attrs);
|
|
227
|
+
const signIn = (0, import_react.useRef)(onSignIn);
|
|
228
|
+
signIn.current = onSignIn;
|
|
229
|
+
const hasSignIn = onSignIn !== void 0;
|
|
230
|
+
(0, import_react.useEffect)(() => {
|
|
231
|
+
if (!isBrowser() || !hasSignIn) return;
|
|
232
|
+
let active = true;
|
|
233
|
+
const handler = () => active && signIn.current ? signIn.current() : false;
|
|
234
|
+
setThesisSignInHandler(handler, scriptSrc).catch(() => void 0);
|
|
235
|
+
return () => {
|
|
236
|
+
active = false;
|
|
237
|
+
setThesisSignInHandler(null, scriptSrc).catch(() => void 0);
|
|
238
|
+
};
|
|
239
|
+
}, [hasSignIn, scriptSrc]);
|
|
187
240
|
(0, import_react.useEffect)(() => {
|
|
188
241
|
if (!isBrowser()) return;
|
|
189
242
|
let cancelled = false;
|
package/dist/react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx","../src/widget.ts"],"sourcesContent":["/**\r\n * React bindings for \"Login with CabalSpy ID\" and the token theses widget.\r\n *\r\n * import { CabalSpyLogin, CabalSpyTheses, useCabalSpyID } from 'cabalspy-id/react';\r\n *\r\n * <CabalSpyLogin clientId=\"csid_…\" onLogin={({ idToken }) => sendToBackend(idToken)} />\r\n * <CabalSpyTheses chain=\"solana\" mint={mint} />\r\n *\r\n * Loads https://id.cabalspy.xyz/widget.js once and maps props to its data-*\r\n * attributes (the same mapping as `cabalspy-id/widget`). Always verify `idToken`\r\n * on your backend (CabalSpyIDAuth.verifyIdToken) before you trust it.\r\n */\r\n\r\nimport { type CSSProperties, createElement, useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\r\n\r\nimport type { CabalSpyIDClaims, OidcScope } from './types';\r\nimport {\r\n type CabalSpyIDErrorEvent,\r\n type CabalSpyIDLogin,\r\n type CabalSpyIDLogoutEvent,\r\n type CabalSpyIDUserSummary,\r\n type LoginButtonOptions,\r\n type ThesisWidgetOptions,\r\n isBrowser,\r\n loadCabalSpyIDWidget,\r\n loginButtonAttributes,\r\n onWidgetEvents,\r\n thesisWidgetAttributes,\r\n} from './widget';\r\n\r\nexport {\r\n WIDGET_SRC,\r\n loadCabalSpyIDWidget,\r\n type CabalSpyIDLogin,\r\n type CabalSpyIDLogoutEvent,\r\n type CabalSpyIDErrorEvent,\r\n type CabalSpyIDUserSummary,\r\n type CSIDGlobal,\r\n type LoginButtonOptions,\r\n type ThesisWidgetOptions,\r\n} from './widget';\r\n\r\n// ── <CabalSpyLogin /> ─────────────────────────────────────────────────────────\r\n\r\nexport interface CabalSpyLoginProps extends LoginButtonOptions {\r\n /** Fired after a browser-app login (window event `csid:login`). */\r\n onLogin?: (result: CabalSpyIDLogin) => void;\r\n /** Fired when the person signs out from the menu (window event `csid:logout`). */\r\n onLogout?: (event: CabalSpyIDLogoutEvent) => void;\r\n /** Fired when the browser flow fails (window event `csid:error`). */\r\n onError?: (event: CabalSpyIDErrorEvent) => void;\r\n /** Override the widget URL (e.g. for a self-hosted copy). */\r\n scriptSrc?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n id?: string;\r\n}\r\n\r\n/** \"Sign in with CabalSpy ID\" button (the official widget). */\r\nexport function CabalSpyLogin(props: CabalSpyLoginProps) {\r\n const { clientId, onLogin, onLogout, onError, scriptSrc } = props;\r\n const handlers = useRef({ onLogin, onLogout, onError });\r\n handlers.current = { onLogin, onLogout, onError };\r\n\r\n useEffect(\r\n () =>\r\n onWidgetEvents(\r\n {\r\n onLogin: (d) => handlers.current.onLogin?.(d),\r\n onLogout: (d) => handlers.current.onLogout?.(d),\r\n onError: (d) => handlers.current.onError?.(d),\r\n },\r\n clientId,\r\n ),\r\n [clientId],\r\n );\r\n\r\n const attrs = loginButtonAttributes(props);\r\n const key = JSON.stringify(attrs);\r\n\r\n useEffect(() => {\r\n if (!isBrowser()) return;\r\n let cancelled = false;\r\n loadCabalSpyIDWidget(scriptSrc).then(\r\n (csid) => {\r\n if (!cancelled) csid.mount();\r\n },\r\n (err: Error) => handlers.current.onError?.({ error: 'widget_load_failed', description: err.message }),\r\n );\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [key, scriptSrc]);\r\n\r\n return createElement('div', { ...attrs, id: props.id, className: props.className, style: props.style });\r\n}\r\n\r\n// ── <CabalSpyTheses /> ────────────────────────────────────────────────────────\r\n\r\nexport interface CabalSpyThesesProps extends ThesisWidgetOptions {\r\n /**\r\n * The signed-in user's CabalSpy ID access token (scope `thesis`), so they\r\n * write without signing in again inside the widget. After a browser login\r\n * through the widget this is set automatically. `null` signs the widget out.\r\n */\r\n accessToken?: string | null;\r\n scriptSrc?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n id?: string;\r\n}\r\n\r\n/**\r\n * Token theses widget: holders' theses on a token with their real position,\r\n * replies, reactions, live updates.\r\n *\r\n * <CabalSpyTheses chain=\"solana\" mint={mint} theme=\"dark\" accessToken={accessToken} />\r\n */\r\nexport function CabalSpyTheses(props: CabalSpyThesesProps) {\r\n const { accessToken, scriptSrc } = props;\r\n const attrs = thesisWidgetAttributes(props);\r\n const key = JSON.stringify(attrs);\r\n\r\n useEffect(() => {\r\n if (!isBrowser()) return;\r\n let cancelled = false;\r\n loadCabalSpyIDWidget(scriptSrc).then(\r\n (csid) => {\r\n if (!cancelled) csid.mount();\r\n },\r\n () => undefined,\r\n );\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [key, scriptSrc]);\r\n\r\n useEffect(() => {\r\n if (!isBrowser() || accessToken === undefined) return;\r\n let cancelled = false;\r\n loadCabalSpyIDWidget(scriptSrc).then(\r\n (csid) => {\r\n if (!cancelled) csid.thesis?.setAccessToken(accessToken);\r\n },\r\n () => undefined,\r\n );\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [accessToken, scriptSrc]);\r\n\r\n // The widget mounts each element once; a new key gives a new element (and\r\n // iframe) when the token or the design changes.\r\n return createElement('div', {\r\n key,\r\n ...attrs,\r\n id: props.id,\r\n className: props.className,\r\n style: props.style,\r\n });\r\n}\r\n\r\n// ── useCabalSpyID() ───────────────────────────────────────────────────────────\r\n\r\ninterface AuthState {\r\n login: CabalSpyIDLogin | null;\r\n}\r\n\r\nlet state: AuthState = { login: null };\r\nconst subscribers = new Set<() => void>();\r\nlet listening = false;\r\n\r\nfunction setState(next: AuthState) {\r\n state = next;\r\n for (const s of subscribers) s();\r\n}\r\n\r\nfunction listen() {\r\n if (listening || !isBrowser()) return;\r\n listening = true;\r\n if (window.CSID?.user) state = { login: window.CSID.user };\r\n window.addEventListener('csid:login', (e) => setState({ login: (e as CustomEvent<CabalSpyIDLogin>).detail }));\r\n window.addEventListener('csid:logout', () => setState({ login: null }));\r\n}\r\n\r\nfunction subscribe(cb: () => void) {\r\n listen();\r\n subscribers.add(cb);\r\n return () => {\r\n subscribers.delete(cb);\r\n };\r\n}\r\n\r\nconst getSnapshot = () => state;\r\nconst getServerSnapshot = () => state;\r\n\r\nfunction remembered(clientId: string | undefined): CabalSpyIDUserSummary | null {\r\n if (!clientId || !isBrowser()) return null;\r\n try {\r\n return JSON.parse(localStorage.getItem(`csid:user:${clientId}`) ?? 'null') as CabalSpyIDUserSummary | null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport interface UseCabalSpyIDOptions {\r\n /** Your client_id; needed for login(), signOut() and the remembered user. */\r\n clientId?: string;\r\n /** Load widget.js on mount (so a returning OAuth redirect completes). Default: true */\r\n load?: boolean;\r\n scriptSrc?: string;\r\n}\r\n\r\nexport interface UseCabalSpyIDResult {\r\n /** userinfo claims from this page's login, else the person remembered on this device. */\r\n user: CabalSpyIDClaims | CabalSpyIDUserSummary | null;\r\n /** Only right after a browser login on this page. Verify it on your backend. */\r\n idToken: string | null;\r\n accessToken: string | null;\r\n /** True when the platform is over its plan's user limit for this person. */\r\n limited: boolean;\r\n /** widget.js is loaded. */\r\n ready: boolean;\r\n /** Starts the browser (public client) login flow. */\r\n login: (opts?: { redirectUri?: string; scope?: string | OidcScope[] }) => Promise<void>;\r\n /** Forgets the person on this device and fires `csid:logout`. */\r\n signOut: () => void;\r\n}\r\n\r\n/** Login state from the CabalSpy ID widget. */\r\nexport function useCabalSpyID(options: UseCabalSpyIDOptions = {}): UseCabalSpyIDResult {\r\n const { clientId, load = true, scriptSrc } = options;\r\n const snap = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\r\n const [ready, setReady] = useState(() => isBrowser() && !!window.CSID?.__loaded);\r\n const [stored, setStored] = useState<CabalSpyIDUserSummary | null>(null);\r\n\r\n useEffect(() => {\r\n setStored(remembered(clientId));\r\n if (!load || !isBrowser()) return;\r\n let cancelled = false;\r\n loadCabalSpyIDWidget(scriptSrc).then(\r\n () => !cancelled && setReady(true),\r\n () => undefined,\r\n );\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [clientId, load, scriptSrc]);\r\n\r\n useEffect(() => {\r\n if (!snap.login) setStored(remembered(clientId));\r\n }, [snap.login, clientId]);\r\n\r\n const login = useCallback(\r\n async (opts: { redirectUri?: string; scope?: string | OidcScope[] } = {}) => {\r\n if (!clientId) throw new Error('useCabalSpyID: pass { clientId } to use login()');\r\n const csid = await loadCabalSpyIDWidget(scriptSrc);\r\n await csid.login({\r\n clientId,\r\n redirectUri: opts.redirectUri,\r\n scope: Array.isArray(opts.scope) ? opts.scope.join(' ') : opts.scope,\r\n });\r\n },\r\n [clientId, scriptSrc],\r\n );\r\n\r\n const signOut = useCallback(() => {\r\n if (clientId && isBrowser() && window.CSID) window.CSID.signOut(clientId);\r\n else setState({ login: null });\r\n setStored(null);\r\n }, [clientId]);\r\n\r\n const user = snap.login?.user ?? stored;\r\n return {\r\n user,\r\n idToken: snap.login?.idToken ?? null,\r\n accessToken: snap.login?.accessToken ?? null,\r\n limited: !!user?.limited,\r\n ready,\r\n login,\r\n signOut,\r\n };\r\n}\r\n","/**\n * CabalSpy ID widgets for any web page (browser only, no framework):\n * the \"Sign in with CabalSpy ID\" button and the token theses widget.\n *\n * import { mountLoginButton, mountTheses } from 'cabalspy-id/widget';\n *\n * mountLoginButton('#login', { clientId: 'csid_…', onLogin: ({ idToken }) => sendToBackend(idToken) });\n * mountTheses('#theses', { chain: 'solana', mint, clientId: 'csid_…', theme: 'dark' });\n *\n * Loads https://id.cabalspy.xyz/widget.js once and maps options to its data-*\n * attributes. React components: `cabalspy-id/react` (also `cabalspy-id/widget/react`).\n */\n\nimport type { CabalSpyIDClaims, Chain, OidcScope } from './types';\n\nexport const WIDGET_SRC = 'https://id.cabalspy.xyz/widget.js';\n\n/** Detail of the widget's `csid:login` event. */\nexport interface CabalSpyIDLogin {\n /** userinfo claims; `limited: true` (anonymous sub) when the platform is over its plan's user limit. */\n user: CabalSpyIDClaims;\n /** Send this to your backend and verify it there. */\n idToken: string;\n accessToken: string;\n /** Seconds. */\n expiresIn: number;\n}\n\nexport interface CabalSpyIDLogoutEvent {\n clientId: string;\n}\n\nexport interface CabalSpyIDErrorEvent {\n error: string;\n description?: string;\n}\n\n/** The signed-in person as the widget remembers them on this device. */\nexport interface CabalSpyIDUserSummary {\n sub: string;\n username?: string;\n name?: string | null;\n picture?: string | null;\n kol_badge?: boolean;\n limited?: boolean;\n}\n\n/** The global the widget defines (window.CSID). */\nexport interface CSIDGlobal {\n __loaded: true;\n user: CabalSpyIDLogin | null;\n mount(): void;\n login(opts: { clientId: string; redirectUri?: string; scope?: string }): Promise<void>;\n setUser(clientId: string, user: CabalSpyIDUserSummary | CabalSpyIDClaims): void;\n signOut(clientId: string): void;\n onLogin(cb: (r: CabalSpyIDLogin) => void): void;\n /** Thesis widgets on the page (data-csid-thesis). */\n thesis?: {\n /** Lets thesis widgets write as this user (access token with the scope \"thesis\"). */\n setAccessToken(token: string | null): void;\n clear(): void;\n };\n}\n\ndeclare global {\n interface Window {\n CSID?: CSIDGlobal;\n }\n}\n\nexport const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\n\nconst loaders = new Map<string, Promise<CSIDGlobal>>();\n\n/** Loads widget.js once per page. Resolves with window.CSID. */\nexport function loadCabalSpyIDWidget(src: string = WIDGET_SRC): Promise<CSIDGlobal> {\n if (!isBrowser()) return Promise.reject(new Error('CabalSpy ID widget needs a browser'));\n if (window.CSID?.__loaded) return Promise.resolve(window.CSID);\n let p = loaders.get(src);\n // The script tag was removed (e.g. by a test or a head manager): load again.\n if (p && !document.querySelector(`script[src=\"${src}\"]`)) {\n loaders.delete(src);\n p = undefined;\n }\n if (!p) {\n p = new Promise<CSIDGlobal>((resolve, reject) => {\n const done = () => (window.CSID ? resolve(window.CSID) : reject(new Error('widget.js loaded but window.CSID is missing')));\n let script = document.querySelector<HTMLScriptElement>(`script[src=\"${src}\"]`);\n if (script && window.CSID) return done();\n if (!script) {\n script = document.createElement('script');\n script.src = src;\n script.async = true;\n document.head.appendChild(script);\n }\n script.addEventListener('load', done, { once: true });\n script.addEventListener(\n 'error',\n () => {\n loaders.delete(src);\n reject(new Error(`Could not load ${src}`));\n },\n { once: true },\n );\n });\n loaders.set(src, p);\n }\n return p;\n}\n\n/** The `aud` of an (unverified) ID token, to route widget events to the right client. */\nexport function audOf(idToken: string): string | undefined {\n try {\n const part = idToken.split('.')[1]!.replace(/-/g, '+').replace(/_/g, '/');\n const aud = (JSON.parse(atob(part + '='.repeat((4 - (part.length % 4)) % 4))) as { aud?: string | string[] }).aud;\n return Array.isArray(aud) ? aud[0] : aud;\n } catch {\n return undefined;\n }\n}\n\nconst bool = (v: boolean | undefined) => (v === undefined ? undefined : v ? 'true' : 'false');\nconst num = (v: number | undefined) => (v === undefined ? undefined : String(v));\n\n// ── options ──────────────────────────────────────────────────────────────────\n\n/** Design and behaviour of the login button. */\nexport interface LoginButtonOptions {\n /** Your instance's client_id (`csid_…`). */\n clientId: string;\n /** Button design overrides; defaults come from the dashboard's button builder. */\n theme?: 'dark' | 'light' | (string & {});\n color?: string;\n textColor?: string;\n shape?: 'square' | 'rounded' | 'pill';\n size?: 'sm' | 'md' | 'lg';\n label?: string;\n fullWidth?: boolean;\n showLogo?: boolean;\n /**\n * Server-side apps: your own login route that starts the OIDC flow with your\n * client secret. Without it, a public client runs PKCE in the browser.\n */\n href?: string;\n /** Browser apps: redirect URI (default: the current page). */\n redirectUri?: string;\n /**\n * Browser apps: requested scopes (default: openid profile). Add `thesis` so\n * the person can write in thesis widgets on your page right after sign-in.\n */\n scope?: string | OidcScope[];\n /** Server-side apps: the signed-in person's `sub`, to show the avatar menu. */\n user?: string;\n /** Server-side apps: where \"Sign out\" goes. */\n logoutHref?: string;\n}\n\n/** Design and data of the token theses widget. */\nexport interface ThesisWidgetOptions {\n /** solana | bnb | base | eth | rh */\n chain: Chain;\n /** Token mint / contract address. */\n mint: string;\n /**\n * Your instance's client_id: loads the design saved in the dashboard (the\n * options below override it), credits posts to your app and enables `scope: 'instance'`.\n */\n clientId?: string;\n /** Default: dark */\n theme?: 'dark' | 'light';\n /** `global` (default): theses from every platform. `instance`: only the ones written on your instance (needs clientId). */\n scope?: 'global' | 'instance';\n /** Initial order. Default: new */\n sort?: 'new' | 'top';\n /** Font family, e.g. \"Inter\". */\n font?: string;\n /** Corner radius in px. Default: 12 */\n radius?: number;\n /** Colors (CSS colors, e.g. \"#0d0f12\"). */\n bg?: string;\n card?: string;\n text?: string;\n muted?: string;\n border?: string;\n accent?: string;\n accentText?: string;\n bull?: string;\n bear?: string;\n /** Show the token header. Default: true */\n showHeader?: boolean;\n /** Show the bull/bear sentiment bar. */\n showSentiment?: boolean;\n /** Denser layout. Default: false */\n compact?: boolean;\n /** Initial height in px; the widget then sizes itself to its content. Default: 520 */\n height?: number;\n}\n\n/** The widget's data-* attributes for a login button. */\nexport function loginButtonAttributes(o: LoginButtonOptions): Record<string, string | undefined> {\n return {\n 'data-csid-login': '',\n 'data-client-id': o.clientId,\n 'data-theme': o.theme,\n 'data-color': o.color,\n 'data-text-color': o.textColor,\n 'data-shape': o.shape,\n 'data-size': o.size,\n 'data-label': o.label,\n 'data-full-width': bool(o.fullWidth),\n 'data-show-logo': bool(o.showLogo),\n 'data-href': o.href,\n 'data-redirect-uri': o.redirectUri,\n 'data-scope': Array.isArray(o.scope) ? o.scope.join(' ') : o.scope,\n 'data-user': o.user,\n 'data-logout-href': o.logoutHref,\n };\n}\n\n/** The widget's data-* attributes for a theses widget. */\nexport function thesisWidgetAttributes(o: ThesisWidgetOptions): Record<string, string | undefined> {\n return {\n 'data-csid-thesis': '',\n 'data-chain': o.chain,\n 'data-mint': o.mint,\n 'data-client-id': o.clientId,\n 'data-theme': o.theme,\n 'data-scope': o.scope,\n 'data-sort': o.sort,\n 'data-font': o.font,\n 'data-radius': num(o.radius),\n 'data-bg': o.bg,\n 'data-card': o.card,\n 'data-text': o.text,\n 'data-muted': o.muted,\n 'data-border': o.border,\n 'data-accent': o.accent,\n 'data-accent-text': o.accentText,\n 'data-bull': o.bull,\n 'data-bear': o.bear,\n 'data-show-header': bool(o.showHeader),\n 'data-show-sentiment': bool(o.showSentiment),\n 'data-compact': bool(o.compact),\n 'data-height': num(o.height),\n };\n}\n\n// ── events ───────────────────────────────────────────────────────────────────\n\nexport interface WidgetEventHandlers {\n /** A browser-app login finished (window event `csid:login`). */\n onLogin?: (result: CabalSpyIDLogin) => void;\n /** The person signed out from the menu (window event `csid:logout`). */\n onLogout?: (event: CabalSpyIDLogoutEvent) => void;\n /** The browser flow failed (window event `csid:error`). */\n onError?: (event: CabalSpyIDErrorEvent) => void;\n}\n\n/**\n * Listens to the widget's window events. With `clientId`, logins and logouts of\n * other clients on the same page are ignored. Returns a function that stops listening.\n */\nexport function onWidgetEvents(handlers: WidgetEventHandlers, clientId?: string): () => void {\n if (!isBrowser()) return () => undefined;\n const login = (e: Event) => {\n const d = (e as CustomEvent<CabalSpyIDLogin>).detail;\n const aud = d?.idToken ? audOf(d.idToken) : undefined;\n if (clientId && aud !== undefined && aud !== clientId) return;\n handlers.onLogin?.(d);\n };\n const logout = (e: Event) => {\n const d = (e as CustomEvent<CabalSpyIDLogoutEvent>).detail;\n if (clientId && d?.clientId && d.clientId !== clientId) return;\n handlers.onLogout?.(d);\n };\n const error = (e: Event) => handlers.onError?.((e as CustomEvent<CabalSpyIDErrorEvent>).detail);\n window.addEventListener('csid:login', login);\n window.addEventListener('csid:logout', logout);\n window.addEventListener('csid:error', error);\n return () => {\n window.removeEventListener('csid:login', login);\n window.removeEventListener('csid:logout', logout);\n window.removeEventListener('csid:error', error);\n };\n}\n\n// ── mounting (vanilla) ───────────────────────────────────────────────────────\n\nexport interface MountedWidget<O> {\n /** The element carrying the data-* attributes. */\n readonly element: HTMLElement;\n /** Re-renders with new options (a fresh element, so the widget mounts it again). */\n update(options: Partial<O>): void;\n /** Removes the widget and its event listeners. */\n remove(): void;\n}\n\nfunction resolveTarget(target: Element | string): Element {\n if (!isBrowser()) throw new Error('CabalSpy ID widget needs a browser');\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (!el) throw new Error(`CabalSpy ID widget: no element matches ${String(target)}`);\n return el;\n}\n\nfunction applyAttributes(el: HTMLElement, attrs: Record<string, string | undefined>): void {\n for (const [k, v] of Object.entries(attrs)) if (v !== undefined) el.setAttribute(k, v);\n}\n\nfunction mountWith<O>(\n target: Element | string,\n options: O,\n attributes: (o: O) => Record<string, string | undefined>,\n after: (csid: CSIDGlobal, o: O) => void,\n scriptSrc: string | undefined,\n onLoadError?: (err: Error) => void,\n): MountedWidget<O> {\n const host = resolveTarget(target);\n let current = options;\n let el = document.createElement('div');\n const render = () => {\n const next = document.createElement('div');\n applyAttributes(next, attributes(current));\n if (el.parentNode) el.replaceWith(next);\n else host.appendChild(next);\n el = next;\n loadCabalSpyIDWidget(scriptSrc).then(\n (csid) => {\n if (el.isConnected) {\n csid.mount();\n after(csid, current);\n }\n },\n (err: Error) => onLoadError?.(err),\n );\n };\n render();\n return {\n get element() {\n return el;\n },\n update(partial) {\n current = { ...current, ...partial };\n render();\n },\n remove() {\n el.remove();\n },\n };\n}\n\n/**\n * Mounts the \"Sign in with CabalSpy ID\" button into `target` (an element or a\n * CSS selector).\n */\nexport function mountLoginButton(\n target: Element | string,\n options: LoginButtonOptions & WidgetEventHandlers & { scriptSrc?: string },\n): MountedWidget<LoginButtonOptions> {\n const stop = onWidgetEvents(options, options.clientId);\n const mounted = mountWith<LoginButtonOptions>(\n target,\n options,\n loginButtonAttributes,\n () => undefined,\n options.scriptSrc,\n (err) => options.onError?.({ error: 'widget_load_failed', description: err.message }),\n );\n return {\n get element() {\n return mounted.element;\n },\n update: (o) => mounted.update(o),\n remove() {\n stop();\n mounted.remove();\n },\n };\n}\n\n/**\n * Mounts the token theses widget into `target` (an element or a CSS selector).\n * `accessToken` (scope `thesis`) lets the signed-in person write right away.\n */\nexport function mountTheses(\n target: Element | string,\n options: ThesisWidgetOptions & { accessToken?: string | null; scriptSrc?: string },\n): MountedWidget<ThesisWidgetOptions> {\n return mountWith<ThesisWidgetOptions>(\n target,\n options,\n thesisWidgetAttributes,\n (csid) => {\n if (options.accessToken !== undefined) csid.thesis?.setAccessToken(options.accessToken);\n },\n options.scriptSrc,\n );\n}\n\n/**\n * Lets thesis widgets on the page write as this user (an access token with the\n * scope `thesis`), or signs them out with `null`.\n */\nexport async function setThesisAccessToken(token: string | null, scriptSrc?: string): Promise<void> {\n const csid = await loadCabalSpyIDWidget(scriptSrc);\n csid.thesis?.setAccessToken(token);\n}\n\n/** Starts the browser (public client) login flow. */\nexport async function startLogin(\n options: { clientId: string; redirectUri?: string; scope?: string | OidcScope[] },\n scriptSrc?: string,\n): Promise<void> {\n const csid = await loadCabalSpyIDWidget(scriptSrc);\n await csid.login({\n clientId: options.clientId,\n redirectUri: options.redirectUri,\n scope: Array.isArray(options.scope) ? options.scope.join(' ') : options.scope,\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx","../src/errors.ts","../src/widget.ts"],"sourcesContent":["/**\n * React bindings for \"Login with CabalSpy ID\" and the token theses widget.\n *\n * import { CabalSpyLogin, CabalSpyTheses, useCabalSpyID } from 'cabalspy-id/react';\n *\n * <CabalSpyLogin clientId=\"csid_…\" onLogin={({ idToken }) => sendToBackend(idToken)} />\n * <CabalSpyTheses chain=\"solana\" mint={mint} />\n *\n * Loads https://id.cabalspy.xyz/widget.js once and maps props to its data-*\n * attributes (the same mapping as `cabalspy-id/widget`). Always verify `idToken`\n * on your backend (CabalSpyIDAuth.verifyIdToken) before you trust it.\n */\n\nimport { type CSSProperties, createElement, useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\n\nimport type { CabalSpyIDClaims, OidcScope } from './types';\nimport {\n type CabalSpyIDErrorEvent,\n type CabalSpyIDLogin,\n type CabalSpyIDLogoutEvent,\n type CabalSpyIDUserSummary,\n type LoginButtonOptions,\n type ThesisSignInHandler,\n type ThesisWidgetOptions,\n isBrowser,\n loadCabalSpyIDWidget,\n loginButtonAttributes,\n onWidgetEvents,\n setThesisSignInHandler,\n thesisWidgetAttributes,\n} from './widget';\n\nexport {\n WIDGET_SRC,\n loadCabalSpyIDWidget,\n type CabalSpyIDLogin,\n type CabalSpyIDLogoutEvent,\n type CabalSpyIDErrorEvent,\n type CabalSpyIDUserSummary,\n type CSIDGlobal,\n type LoginButtonOptions,\n type ThesisSignInHandler,\n type ThesisWidgetOptions,\n} from './widget';\n\n// ── <CabalSpyLogin /> ─────────────────────────────────────────────────────────\n\nexport interface CabalSpyLoginProps extends LoginButtonOptions {\n /** Fired after a browser-app login (window event `csid:login`). */\n onLogin?: (result: CabalSpyIDLogin) => void;\n /** Fired when the person signs out from the menu (window event `csid:logout`). */\n onLogout?: (event: CabalSpyIDLogoutEvent) => void;\n /** Fired when the browser flow fails (window event `csid:error`). */\n onError?: (event: CabalSpyIDErrorEvent) => void;\n /** Override the widget URL (e.g. for a self-hosted copy). */\n scriptSrc?: string;\n className?: string;\n style?: CSSProperties;\n id?: string;\n}\n\n/** \"Sign in with CabalSpy ID\" button (the official widget). */\nexport function CabalSpyLogin(props: CabalSpyLoginProps) {\n const { clientId, onLogin, onLogout, onError, scriptSrc } = props;\n const handlers = useRef({ onLogin, onLogout, onError });\n handlers.current = { onLogin, onLogout, onError };\n\n useEffect(\n () =>\n onWidgetEvents(\n {\n onLogin: (d) => handlers.current.onLogin?.(d),\n onLogout: (d) => handlers.current.onLogout?.(d),\n onError: (d) => handlers.current.onError?.(d),\n },\n clientId,\n ),\n [clientId],\n );\n\n const attrs = loginButtonAttributes(props);\n const key = JSON.stringify(attrs);\n\n useEffect(() => {\n if (!isBrowser()) return;\n let cancelled = false;\n loadCabalSpyIDWidget(scriptSrc).then(\n (csid) => {\n if (!cancelled) csid.mount();\n },\n (err: Error) => handlers.current.onError?.({ error: 'widget_load_failed', description: err.message }),\n );\n return () => {\n cancelled = true;\n };\n }, [key, scriptSrc]);\n\n return createElement('div', { ...attrs, id: props.id, className: props.className, style: props.style });\n}\n\n// ── <CabalSpyTheses /> ────────────────────────────────────────────────────────\n\nexport interface CabalSpyThesesProps extends ThesisWidgetOptions {\n /**\n * The signed-in user's CabalSpy ID access token (scope `thesis`), so they\n * write without signing in again inside the widget. After a browser login\n * through the widget this is set automatically. `null` signs the widget out.\n */\n accessToken?: string | null;\n /**\n * Your own sign-in for the widget's \"Sign in to write\" (instead of the page's\n * login button / popup): get a thesis-capable access token for the person,\n * pass it on (the `accessToken` prop or `setThesisAccessToken`) and resolve\n * `true`; resolve `false` when they are not signed in. While set, the page is\n * the source of truth for sign-in. Removed on unmount.\n */\n onSignIn?: ThesisSignInHandler;\n scriptSrc?: string;\n className?: string;\n style?: CSSProperties;\n id?: string;\n}\n\n/**\n * Token theses widget: holders' theses on a token with their real position,\n * replies, reactions, live updates.\n *\n * <CabalSpyTheses chain=\"solana\" mint={mint} clientId=\"csid_...\" theme=\"dark\" accessToken={accessToken} />\n */\nexport function CabalSpyTheses(props: CabalSpyThesesProps) {\n const { accessToken, scriptSrc, onSignIn } = props;\n const attrs = thesisWidgetAttributes(props);\n const key = JSON.stringify(attrs);\n const signIn = useRef(onSignIn);\n signIn.current = onSignIn;\n const hasSignIn = onSignIn !== undefined;\n\n useEffect(() => {\n if (!isBrowser() || !hasSignIn) return;\n let active = true;\n // A stable wrapper, so a new onSignIn function does not re-register.\n const handler: ThesisSignInHandler = () => (active && signIn.current ? signIn.current() : false);\n setThesisSignInHandler(handler, scriptSrc).catch(() => undefined);\n return () => {\n active = false;\n setThesisSignInHandler(null, scriptSrc).catch(() => undefined);\n };\n }, [hasSignIn, scriptSrc]);\n\n useEffect(() => {\n if (!isBrowser()) return;\n let cancelled = false;\n loadCabalSpyIDWidget(scriptSrc).then(\n (csid) => {\n if (!cancelled) csid.mount();\n },\n () => undefined,\n );\n return () => {\n cancelled = true;\n };\n }, [key, scriptSrc]);\n\n useEffect(() => {\n if (!isBrowser() || accessToken === undefined) return;\n let cancelled = false;\n loadCabalSpyIDWidget(scriptSrc).then(\n (csid) => {\n if (!cancelled) csid.thesis?.setAccessToken(accessToken);\n },\n () => undefined,\n );\n return () => {\n cancelled = true;\n };\n }, [accessToken, scriptSrc]);\n\n // The widget mounts each element once; a new key gives a new element (and\n // iframe) when the token or the design changes.\n return createElement('div', {\n key,\n ...attrs,\n id: props.id,\n className: props.className,\n style: props.style,\n });\n}\n\n// ── useCabalSpyID() ───────────────────────────────────────────────────────────\n\ninterface AuthState {\n login: CabalSpyIDLogin | null;\n}\n\nlet state: AuthState = { login: null };\nconst subscribers = new Set<() => void>();\nlet listening = false;\n\nfunction setState(next: AuthState) {\n state = next;\n for (const s of subscribers) s();\n}\n\nfunction listen() {\n if (listening || !isBrowser()) return;\n listening = true;\n if (window.CSID?.user) state = { login: window.CSID.user };\n window.addEventListener('csid:login', (e) => setState({ login: (e as CustomEvent<CabalSpyIDLogin>).detail }));\n window.addEventListener('csid:logout', () => setState({ login: null }));\n}\n\nfunction subscribe(cb: () => void) {\n listen();\n subscribers.add(cb);\n return () => {\n subscribers.delete(cb);\n };\n}\n\nconst getSnapshot = () => state;\nconst getServerSnapshot = () => state;\n\nfunction remembered(clientId: string | undefined): CabalSpyIDUserSummary | null {\n if (!clientId || !isBrowser()) return null;\n try {\n return JSON.parse(localStorage.getItem(`csid:user:${clientId}`) ?? 'null') as CabalSpyIDUserSummary | null;\n } catch {\n return null;\n }\n}\n\nexport interface UseCabalSpyIDOptions {\n /** Your client_id; needed for login(), signOut() and the remembered user. */\n clientId?: string;\n /** Load widget.js on mount (so a returning OAuth redirect completes). Default: true */\n load?: boolean;\n scriptSrc?: string;\n}\n\nexport interface UseCabalSpyIDResult {\n /** userinfo claims from this page's login, else the person remembered on this device. */\n user: CabalSpyIDClaims | CabalSpyIDUserSummary | null;\n /** Only right after a browser login on this page. Verify it on your backend. */\n idToken: string | null;\n accessToken: string | null;\n /** True when the platform is over its plan's user limit for this person. */\n limited: boolean;\n /** widget.js is loaded. */\n ready: boolean;\n /** Starts the browser (public client) login flow. */\n login: (opts?: { redirectUri?: string; scope?: string | OidcScope[] }) => Promise<void>;\n /** Forgets the person on this device and fires `csid:logout`. */\n signOut: () => void;\n}\n\n/** Login state from the CabalSpy ID widget. */\nexport function useCabalSpyID(options: UseCabalSpyIDOptions = {}): UseCabalSpyIDResult {\n const { clientId, load = true, scriptSrc } = options;\n const snap = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n const [ready, setReady] = useState(() => isBrowser() && !!window.CSID?.__loaded);\n const [stored, setStored] = useState<CabalSpyIDUserSummary | null>(null);\n\n useEffect(() => {\n setStored(remembered(clientId));\n if (!load || !isBrowser()) return;\n let cancelled = false;\n loadCabalSpyIDWidget(scriptSrc).then(\n () => !cancelled && setReady(true),\n () => undefined,\n );\n return () => {\n cancelled = true;\n };\n }, [clientId, load, scriptSrc]);\n\n useEffect(() => {\n if (!snap.login) setStored(remembered(clientId));\n }, [snap.login, clientId]);\n\n const login = useCallback(\n async (opts: { redirectUri?: string; scope?: string | OidcScope[] } = {}) => {\n if (!clientId) throw new Error('useCabalSpyID: pass { clientId } to use login()');\n const csid = await loadCabalSpyIDWidget(scriptSrc);\n await csid.login({\n clientId,\n redirectUri: opts.redirectUri,\n scope: Array.isArray(opts.scope) ? opts.scope.join(' ') : opts.scope,\n });\n },\n [clientId, scriptSrc],\n );\n\n const signOut = useCallback(() => {\n if (clientId && isBrowser() && window.CSID) window.CSID.signOut(clientId);\n else setState({ login: null });\n setStored(null);\n }, [clientId]);\n\n const user = snap.login?.user ?? stored;\n return {\n user,\n idToken: snap.login?.idToken ?? null,\n accessToken: snap.login?.accessToken ?? null,\n limited: !!user?.limited,\n ready,\n login,\n signOut,\n };\n}\n","// ═══════════════════════════════════════════════════════════════════════════\n// ERRORS\n// ═══════════════════════════════════════════════════════════════════════════\n\nexport interface RateLimitInfo {\n /** Requests per second allowed by the plan. `null` when the header is absent. */\n limit: number | null;\n remaining: number | null;\n /** Seconds until the current one-second window resets. */\n reset: number | null;\n}\n\nexport const EMPTY_RATE_LIMIT: RateLimitInfo = Object.freeze({ limit: null, remaining: null, reset: null });\n\n/** Error body of the Data API: `{\"success\": false, \"error\": {...}}`. */\nexport interface ApiErrorBody {\n code: string;\n message: string;\n request_id: string;\n /** Link to the error's documentation, e.g. https://docs.cabalspy.xyz/cabalspy-id/errors#wallet_not_found */\n docs: string;\n parameter?: string;\n allowed?: unknown[];\n /** Set on `plan_user_limit`. */\n limited?: boolean;\n [key: string]: unknown;\n}\n\nexport interface CabalSpyIDErrorOptions {\n status?: number;\n code?: string;\n requestId?: string;\n docs?: string;\n parameter?: string;\n allowed?: unknown[];\n rateLimit?: RateLimitInfo;\n body?: unknown;\n}\n\n/** Base class for every error thrown by this SDK. */\nexport class CabalSpyIDError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId?: string;\n readonly docs?: string;\n readonly parameter?: string;\n readonly allowed?: unknown[];\n readonly rateLimit: RateLimitInfo;\n /** The parsed response body, when there was one. */\n readonly body?: unknown;\n\n constructor(message: string, opts: CabalSpyIDErrorOptions = {}) {\n super(message);\n this.name = new.target.name;\n this.status = opts.status ?? 0;\n this.code = opts.code ?? 'unknown_error';\n this.requestId = opts.requestId;\n this.docs = opts.docs;\n this.parameter = opts.parameter;\n this.allowed = opts.allowed;\n this.rateLimit = opts.rateLimit ?? { ...EMPTY_RATE_LIMIT };\n this.body = opts.body;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** 400 — missing_parameter, invalid_parameter, instance_required */\nexport class BadRequestError extends CabalSpyIDError {}\n/** 401 — missing_api_key; thesis API: client_id_required, invalid_client */\nexport class AuthenticationError extends CabalSpyIDError {}\n/** 403 — invalid_api_key, product_not_enabled, no_instance, scope_not_allowed; thesis API: origin_not_allowed */\nexport class PermissionError extends CabalSpyIDError {}\n/**\n * 403 — plan_user_limit. The person signed in beyond the plan's user limit:\n * the platform receives no data for them until it upgrades.\n */\nexport class PlanUserLimitError extends PermissionError {\n readonly limited = true as const;\n}\n/** 404 — profile_not_found, wallet_not_found, instance_not_found */\nexport class NotFoundError extends CabalSpyIDError {}\n/** 429 — rate_limit_exceeded; thesis API: rate_limited (Retry-After 60) */\nexport class RateLimitError extends CabalSpyIDError {\n /** Seconds until the next request is allowed, taken from Retry-After. */\n readonly retryAfter: number | null;\n constructor(message: string, opts: CabalSpyIDErrorOptions & { retryAfter?: number | null } = {}) {\n super(message, opts);\n this.retryAfter = opts.retryAfter ?? null;\n }\n}\n/** 5xx — internal_error, service_unavailable */\nexport class ServerError extends CabalSpyIDError {}\n/** Network failure, timeout or aborted connection. */\nexport class ConnectionError extends CabalSpyIDError {}\n/** Response was not valid JSON, or did not have the expected envelope. */\nexport class InvalidResponseError extends CabalSpyIDError {}\n\n/**\n * An error from the OpenID Connect endpoints (`/oauth/token`, `/oauth/userinfo`,\n * `/oauth/revoke`) or from the authorization redirect, in OAuth's own format:\n * `code` is the OAuth `error` (invalid_grant, invalid_client, access_denied …).\n */\nexport class OAuthError extends CabalSpyIDError {\n readonly error: string;\n readonly errorDescription?: string;\n constructor(error: string, description?: string, opts: CabalSpyIDErrorOptions = {}) {\n super(description ? `${error}: ${description}` : error, { ...opts, code: error });\n this.error = error;\n this.errorDescription = description;\n }\n}\n\n/** An ID or access token failed verification (signature, iss, aud, exp, nonce …). */\nexport class TokenVerificationError extends CabalSpyIDError {}\n\n/** A webhook failed signature or timestamp verification. */\nexport class WebhookVerificationError extends CabalSpyIDError {}\n\n/** Maps an HTTP status and Data API error body onto the right error class. */\nexport function errorFromStatus(\n status: number,\n body: ApiErrorBody | null,\n rateLimit: RateLimitInfo,\n retryAfter: number | null,\n fallbackMessage: string,\n): CabalSpyIDError {\n const opts: CabalSpyIDErrorOptions = {\n status,\n code: body?.code ?? `http_${status}`,\n requestId: body?.request_id,\n docs: body?.docs,\n parameter: body?.parameter,\n allowed: body?.allowed,\n rateLimit,\n body: body ?? undefined,\n };\n const msg = body?.message ?? fallbackMessage;\n\n if (status === 400) return new BadRequestError(msg, opts);\n if (status === 401) return new AuthenticationError(msg, opts);\n if (status === 403) {\n return body?.code === 'plan_user_limit' ? new PlanUserLimitError(msg, opts) : new PermissionError(msg, opts);\n }\n if (status === 404) return new NotFoundError(msg, opts);\n if (status === 429) return new RateLimitError(msg, { ...opts, retryAfter });\n if (status >= 500) return new ServerError(msg, opts);\n return new CabalSpyIDError(msg, opts);\n}\n\nexport function isRetryable(err: unknown): boolean {\n return err instanceof RateLimitError || err instanceof ServerError || err instanceof ConnectionError;\n}\n","/**\n * CabalSpy ID widgets for any web page (browser only, no framework):\n * the \"Sign in with CabalSpy ID\" button and the token theses widget.\n *\n * import { mountLoginButton, mountTheses } from 'cabalspy-id/widget';\n *\n * mountLoginButton('#login', { clientId: 'csid_…', onLogin: ({ idToken }) => sendToBackend(idToken) });\n * mountTheses('#theses', { chain: 'solana', mint, clientId: 'csid_…', theme: 'dark' });\n *\n * Loads https://id.cabalspy.xyz/widget.js once and maps options to its data-*\n * attributes. React components: `cabalspy-id/react` (also `cabalspy-id/widget/react`).\n */\n\nimport { AuthenticationError } from './errors';\nimport type { CabalSpyIDClaims, Chain, OidcScope } from './types';\n\nexport const WIDGET_SRC = 'https://id.cabalspy.xyz/widget.js';\n\n/** Detail of the widget's `csid:login` event. */\nexport interface CabalSpyIDLogin {\n /** userinfo claims; `limited: true` (anonymous sub) when the platform is over its plan's user limit. */\n user: CabalSpyIDClaims;\n /** Send this to your backend and verify it there. */\n idToken: string;\n accessToken: string;\n /** Seconds. */\n expiresIn: number;\n}\n\nexport interface CabalSpyIDLogoutEvent {\n clientId: string;\n}\n\nexport interface CabalSpyIDErrorEvent {\n error: string;\n description?: string;\n}\n\n/** The signed-in person as the widget remembers them on this device. */\nexport interface CabalSpyIDUserSummary {\n sub: string;\n username?: string;\n name?: string | null;\n picture?: string | null;\n kol_badge?: boolean;\n limited?: boolean;\n}\n\n/** The global the widget defines (window.CSID). */\nexport interface CSIDGlobal {\n __loaded: true;\n user: CabalSpyIDLogin | null;\n mount(): void;\n login(opts: { clientId: string; redirectUri?: string; scope?: string }): Promise<void>;\n setUser(clientId: string, user: CabalSpyIDUserSummary | CabalSpyIDClaims): void;\n signOut(clientId: string): void;\n onLogin(cb: (r: CabalSpyIDLogin) => void): void;\n /** Thesis widgets on the page (data-csid-thesis). */\n thesis?: {\n /** Lets thesis widgets write as this user (access token with the scope \"thesis\"). */\n setAccessToken(token: string | null): void;\n /**\n * Your own sign-in for the widget's \"Sign in to write\" (null removes it).\n * See {@link setThesisSignInHandler}.\n */\n setSignInHandler?(handler: ThesisSignInHandler | null): void;\n clear(): void;\n };\n}\n\n/**\n * Your page's sign-in for thesis widgets: obtain a thesis-capable access token\n * for the person (e.g. your platform's own CabalSpy ID access token with the\n * scope \"thesis\", from your backend), call `setThesisAccessToken(token)` (or\n * `CSID.thesis.setAccessToken(token)`) and resolve `true`; resolve `false` when\n * the person is not signed in.\n */\nexport type ThesisSignInHandler = () => Promise<boolean> | boolean;\n\ndeclare global {\n interface Window {\n CSID?: CSIDGlobal;\n }\n}\n\nexport const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\n\nconst loaders = new Map<string, Promise<CSIDGlobal>>();\n\n/** Loads widget.js once per page. Resolves with window.CSID. */\nexport function loadCabalSpyIDWidget(src: string = WIDGET_SRC): Promise<CSIDGlobal> {\n if (!isBrowser()) return Promise.reject(new Error('CabalSpy ID widget needs a browser'));\n if (window.CSID?.__loaded) return Promise.resolve(window.CSID);\n let p = loaders.get(src);\n // The script tag was removed (e.g. by a test or a head manager): load again.\n if (p && !document.querySelector(`script[src=\"${src}\"]`)) {\n loaders.delete(src);\n p = undefined;\n }\n if (!p) {\n p = new Promise<CSIDGlobal>((resolve, reject) => {\n const done = () => (window.CSID ? resolve(window.CSID) : reject(new Error('widget.js loaded but window.CSID is missing')));\n let script = document.querySelector<HTMLScriptElement>(`script[src=\"${src}\"]`);\n if (script && window.CSID) return done();\n if (!script) {\n script = document.createElement('script');\n script.src = src;\n script.async = true;\n document.head.appendChild(script);\n }\n script.addEventListener('load', done, { once: true });\n script.addEventListener(\n 'error',\n () => {\n loaders.delete(src);\n reject(new Error(`Could not load ${src}`));\n },\n { once: true },\n );\n });\n loaders.set(src, p);\n }\n return p;\n}\n\n/** The `aud` of an (unverified) ID token, to route widget events to the right client. */\nexport function audOf(idToken: string): string | undefined {\n try {\n const part = idToken.split('.')[1]!.replace(/-/g, '+').replace(/_/g, '/');\n const aud = (JSON.parse(atob(part + '='.repeat((4 - (part.length % 4)) % 4))) as { aud?: string | string[] }).aud;\n return Array.isArray(aud) ? aud[0] : aud;\n } catch {\n return undefined;\n }\n}\n\nconst bool = (v: boolean | undefined) => (v === undefined ? undefined : v ? 'true' : 'false');\nconst num = (v: number | undefined) => (v === undefined ? undefined : String(v));\n\n// ── options ──────────────────────────────────────────────────────────────────\n\n/** Design and behaviour of the login button. */\nexport interface LoginButtonOptions {\n /** Your instance's client_id (`csid_…`). */\n clientId: string;\n /** Button design overrides; defaults come from the dashboard's button builder. */\n theme?: 'dark' | 'light' | (string & {});\n color?: string;\n textColor?: string;\n shape?: 'square' | 'rounded' | 'pill';\n size?: 'sm' | 'md' | 'lg';\n label?: string;\n fullWidth?: boolean;\n showLogo?: boolean;\n /**\n * Server-side apps: your own login route that starts the OIDC flow with your\n * client secret. Without it, a public client runs PKCE in the browser.\n */\n href?: string;\n /** Browser apps: redirect URI (default: the current page). */\n redirectUri?: string;\n /**\n * Browser apps: requested scopes (default: openid profile). Add `thesis` so\n * the person can write in thesis widgets on your page right after sign-in.\n */\n scope?: string | OidcScope[];\n /** Server-side apps: the signed-in person's `sub`, to show the avatar menu. */\n user?: string;\n /** Server-side apps: where \"Sign out\" goes. */\n logoutHref?: string;\n}\n\n/** Design and data of the token theses widget. */\nexport interface ThesisWidgetOptions {\n /** solana | bnb | base | eth | rh */\n chain: Chain;\n /** Token mint / contract address. */\n mint: string;\n /**\n * Required. Your instance's PUBLIC client_id (`csid_...`, shown in the\n * dashboard next to the API key; not the API key). The widget sends it on\n * every thesis API call; it also loads the design saved in the dashboard (the\n * options below override it), credits posts to your app and enables `scope: 'instance'`.\n */\n clientId: string;\n /** Default: dark */\n theme?: 'dark' | 'light';\n /** `global` (default): theses from every platform. `instance`: only the ones written on your instance. */\n scope?: 'global' | 'instance';\n /** Initial order. Default: new */\n sort?: 'new' | 'top';\n /** Font family, e.g. \"Inter\". */\n font?: string;\n /** Corner radius in px. Default: 12 */\n radius?: number;\n /** Colors (CSS colors, e.g. \"#0d0f12\"). */\n bg?: string;\n card?: string;\n text?: string;\n muted?: string;\n border?: string;\n accent?: string;\n accentText?: string;\n bull?: string;\n bear?: string;\n /** Show the token header. Default: true */\n showHeader?: boolean;\n /** Show the bull/bear sentiment bar. */\n showSentiment?: boolean;\n /** Denser layout. Default: false */\n compact?: boolean;\n /** Initial height in px; the widget then sizes itself to its content. Default: 520 */\n height?: number;\n}\n\n/** The widget's data-* attributes for a login button. */\nexport function loginButtonAttributes(o: LoginButtonOptions): Record<string, string | undefined> {\n return {\n 'data-csid-login': '',\n 'data-client-id': o.clientId,\n 'data-theme': o.theme,\n 'data-color': o.color,\n 'data-text-color': o.textColor,\n 'data-shape': o.shape,\n 'data-size': o.size,\n 'data-label': o.label,\n 'data-full-width': bool(o.fullWidth),\n 'data-show-logo': bool(o.showLogo),\n 'data-href': o.href,\n 'data-redirect-uri': o.redirectUri,\n 'data-scope': Array.isArray(o.scope) ? o.scope.join(' ') : o.scope,\n 'data-user': o.user,\n 'data-logout-href': o.logoutHref,\n };\n}\n\n/** The widget's data-* attributes for a theses widget. Throws `client_id_required` without `clientId`. */\nexport function thesisWidgetAttributes(o: ThesisWidgetOptions): Record<string, string | undefined> {\n if (typeof o?.clientId !== 'string' || !o.clientId.trim()) {\n throw new AuthenticationError(\n \"The theses widget needs your instance's public client_id (csid_..., shown in the dashboard next to the API key): \" +\n \"{ clientId: 'csid_...' }. It is not the API key; never put the API key in a browser.\",\n { code: 'client_id_required', parameter: 'clientId', docs: 'https://docs.cabalspy.xyz/cabalspy-id/errors#client_id_required' },\n );\n }\n return {\n 'data-csid-thesis': '',\n 'data-chain': o.chain,\n 'data-mint': o.mint,\n 'data-client-id': o.clientId.trim(),\n 'data-theme': o.theme,\n 'data-scope': o.scope,\n 'data-sort': o.sort,\n 'data-font': o.font,\n 'data-radius': num(o.radius),\n 'data-bg': o.bg,\n 'data-card': o.card,\n 'data-text': o.text,\n 'data-muted': o.muted,\n 'data-border': o.border,\n 'data-accent': o.accent,\n 'data-accent-text': o.accentText,\n 'data-bull': o.bull,\n 'data-bear': o.bear,\n 'data-show-header': bool(o.showHeader),\n 'data-show-sentiment': bool(o.showSentiment),\n 'data-compact': bool(o.compact),\n 'data-height': num(o.height),\n };\n}\n\n// ── events ───────────────────────────────────────────────────────────────────\n\nexport interface WidgetEventHandlers {\n /** A browser-app login finished (window event `csid:login`). */\n onLogin?: (result: CabalSpyIDLogin) => void;\n /** The person signed out from the menu (window event `csid:logout`). */\n onLogout?: (event: CabalSpyIDLogoutEvent) => void;\n /** The browser flow failed (window event `csid:error`). */\n onError?: (event: CabalSpyIDErrorEvent) => void;\n}\n\n/**\n * Listens to the widget's window events. With `clientId`, logins and logouts of\n * other clients on the same page are ignored. Returns a function that stops listening.\n */\nexport function onWidgetEvents(handlers: WidgetEventHandlers, clientId?: string): () => void {\n if (!isBrowser()) return () => undefined;\n const login = (e: Event) => {\n const d = (e as CustomEvent<CabalSpyIDLogin>).detail;\n const aud = d?.idToken ? audOf(d.idToken) : undefined;\n if (clientId && aud !== undefined && aud !== clientId) return;\n handlers.onLogin?.(d);\n };\n const logout = (e: Event) => {\n const d = (e as CustomEvent<CabalSpyIDLogoutEvent>).detail;\n if (clientId && d?.clientId && d.clientId !== clientId) return;\n handlers.onLogout?.(d);\n };\n const error = (e: Event) => handlers.onError?.((e as CustomEvent<CabalSpyIDErrorEvent>).detail);\n window.addEventListener('csid:login', login);\n window.addEventListener('csid:logout', logout);\n window.addEventListener('csid:error', error);\n return () => {\n window.removeEventListener('csid:login', login);\n window.removeEventListener('csid:logout', logout);\n window.removeEventListener('csid:error', error);\n };\n}\n\n// ── mounting (vanilla) ───────────────────────────────────────────────────────\n\nexport interface MountedWidget<O> {\n /** The element carrying the data-* attributes. */\n readonly element: HTMLElement;\n /** Re-renders with new options (a fresh element, so the widget mounts it again). */\n update(options: Partial<O>): void;\n /** Removes the widget and its event listeners. */\n remove(): void;\n}\n\nfunction resolveTarget(target: Element | string): Element {\n if (!isBrowser()) throw new Error('CabalSpy ID widget needs a browser');\n const el = typeof target === 'string' ? document.querySelector(target) : target;\n if (!el) throw new Error(`CabalSpy ID widget: no element matches ${String(target)}`);\n return el;\n}\n\nfunction applyAttributes(el: HTMLElement, attrs: Record<string, string | undefined>): void {\n for (const [k, v] of Object.entries(attrs)) if (v !== undefined) el.setAttribute(k, v);\n}\n\nfunction mountWith<O>(\n target: Element | string,\n options: O,\n attributes: (o: O) => Record<string, string | undefined>,\n after: (csid: CSIDGlobal, o: O) => void,\n scriptSrc: string | undefined,\n onLoadError?: (err: Error) => void,\n): MountedWidget<O> {\n const host = resolveTarget(target);\n let current = options;\n let el = document.createElement('div');\n const render = () => {\n const next = document.createElement('div');\n applyAttributes(next, attributes(current));\n if (el.parentNode) el.replaceWith(next);\n else host.appendChild(next);\n el = next;\n loadCabalSpyIDWidget(scriptSrc).then(\n (csid) => {\n if (el.isConnected) {\n csid.mount();\n after(csid, current);\n }\n },\n (err: Error) => onLoadError?.(err),\n );\n };\n render();\n return {\n get element() {\n return el;\n },\n update(partial) {\n current = { ...current, ...partial };\n render();\n },\n remove() {\n el.remove();\n },\n };\n}\n\n/**\n * Mounts the \"Sign in with CabalSpy ID\" button into `target` (an element or a\n * CSS selector).\n */\nexport function mountLoginButton(\n target: Element | string,\n options: LoginButtonOptions & WidgetEventHandlers & { scriptSrc?: string },\n): MountedWidget<LoginButtonOptions> {\n const stop = onWidgetEvents(options, options.clientId);\n const mounted = mountWith<LoginButtonOptions>(\n target,\n options,\n loginButtonAttributes,\n () => undefined,\n options.scriptSrc,\n (err) => options.onError?.({ error: 'widget_load_failed', description: err.message }),\n );\n return {\n get element() {\n return mounted.element;\n },\n update: (o) => mounted.update(o),\n remove() {\n stop();\n mounted.remove();\n },\n };\n}\n\n/**\n * Mounts the token theses widget into `target` (an element or a CSS selector).\n * `clientId` (your instance's public client_id) is required. `accessToken`\n * (scope `thesis`) lets the signed-in person write right away.\n */\nexport function mountTheses(\n target: Element | string,\n options: ThesisWidgetOptions & { accessToken?: string | null; scriptSrc?: string },\n): MountedWidget<ThesisWidgetOptions> {\n return mountWith<ThesisWidgetOptions>(\n target,\n options,\n thesisWidgetAttributes,\n (csid) => {\n if (options.accessToken !== undefined) csid.thesis?.setAccessToken(options.accessToken);\n },\n options.scriptSrc,\n );\n}\n\n/**\n * Lets thesis widgets on the page write as this user (an access token with the\n * scope `thesis`), or signs them out with `null`.\n */\nexport async function setThesisAccessToken(token: string | null, scriptSrc?: string): Promise<void> {\n const csid = await loadCabalSpyIDWidget(scriptSrc);\n csid.thesis?.setAccessToken(token);\n}\n\n/**\n * Lets your page handle the thesis widgets' \"Sign in to write\": the widget\n * calls `handler` instead of the page's login button / popup. While a handler\n * is set, the widget treats the page as the source of truth for sign-in (signed\n * out on the page = signed out in the widget). `null` removes it. Waits for\n * widget.js when it is not loaded yet; a no-op on the server.\n *\n * setThesisSignInHandler(async () => {\n * const token = await fetch('/api/csid-thesis-token').then((r) => (r.ok ? r.text() : null));\n * if (!token) return false;\n * await setThesisAccessToken(token);\n * return true;\n * });\n */\nexport async function setThesisSignInHandler(handler: ThesisSignInHandler | null, scriptSrc?: string): Promise<void> {\n if (!isBrowser()) return; // server rendering: nothing to register\n const csid = await loadCabalSpyIDWidget(scriptSrc);\n csid.thesis?.setSignInHandler?.(handler);\n}\n\n/** Starts the browser (public client) login flow. */\nexport async function startLogin(\n options: { clientId: string; redirectUri?: string; scope?: string | OidcScope[] },\n scriptSrc?: string,\n): Promise<void> {\n const csid = await loadCabalSpyIDWidget(scriptSrc);\n await csid.login({\n clientId: options.clientId,\n redirectUri: options.redirectUri,\n scope: Array.isArray(options.scope) ? options.scope.join(' ') : options.scope,\n });\n}\n"],"mappings":"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","names":[]}
|