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.
Files changed (57) hide show
  1. package/CHANGELOG.md +146 -112
  2. package/LICENSE +21 -21
  3. package/README.md +505 -475
  4. package/dist/api.cjs +5 -3
  5. package/dist/api.cjs.map +1 -1
  6. package/dist/api.d.cts +1 -1
  7. package/dist/api.d.ts +1 -1
  8. package/dist/api.js +16 -14
  9. package/dist/chunk-A2ZAFAWC.cjs.map +1 -1
  10. package/dist/chunk-ABFIPCBY.cjs +109 -0
  11. package/dist/chunk-ABFIPCBY.cjs.map +1 -0
  12. package/dist/{chunk-Z3UPDOBY.js → chunk-DBYCRBVS.js} +9 -7
  13. package/dist/chunk-DBYCRBVS.js.map +1 -0
  14. package/dist/{chunk-P3MDLWLN.cjs → chunk-PKFIMBPW.cjs} +113 -54
  15. package/dist/chunk-PKFIMBPW.cjs.map +1 -0
  16. package/dist/{chunk-L4RHSWOA.cjs → chunk-ULEBNY2A.cjs} +121 -212
  17. package/dist/chunk-ULEBNY2A.cjs.map +1 -0
  18. package/dist/{chunk-X3OFGGSE.cjs → chunk-VDFCMYLR.cjs} +50 -48
  19. package/dist/chunk-VDFCMYLR.cjs.map +1 -0
  20. package/dist/chunk-VOTYIQAY.js +109 -0
  21. package/dist/chunk-VOTYIQAY.js.map +1 -0
  22. package/dist/{chunk-3LKLY5WQ.js → chunk-XLBO2RSL.js} +15 -106
  23. package/dist/chunk-XLBO2RSL.js.map +1 -0
  24. package/dist/{chunk-3P7QK2PG.js → chunk-Z23B5TY6.js} +89 -30
  25. package/dist/chunk-Z23B5TY6.js.map +1 -0
  26. package/dist/index.cjs +13 -5
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +3 -3
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +25 -17
  31. package/dist/index.js.map +1 -1
  32. package/dist/react.cjs +55 -2
  33. package/dist/react.cjs.map +1 -1
  34. package/dist/react.d.cts +28 -5
  35. package/dist/react.d.ts +28 -5
  36. package/dist/react.js +55 -2
  37. package/dist/react.js.map +1 -1
  38. package/dist/thesis.cjs +11 -3
  39. package/dist/thesis.cjs.map +1 -1
  40. package/dist/thesis.d.cts +42 -11
  41. package/dist/thesis.d.ts +42 -11
  42. package/dist/thesis.js +24 -16
  43. package/dist/{users-BDLkwW1p.d.cts → users-3DAw88_U.d.cts} +4 -4
  44. package/dist/{users-CDlDRuyv.d.ts → users-B4r33i4O.d.ts} +4 -4
  45. package/dist/widget.cjs +28 -12
  46. package/dist/widget.cjs.map +1 -1
  47. package/dist/widget.d.cts +37 -6
  48. package/dist/widget.d.ts +37 -6
  49. package/dist/widget.js +17 -1
  50. package/dist/widget.js.map +1 -1
  51. package/package.json +147 -147
  52. package/dist/chunk-3LKLY5WQ.js.map +0 -1
  53. package/dist/chunk-3P7QK2PG.js.map +0 -1
  54. package/dist/chunk-L4RHSWOA.cjs.map +0 -1
  55. package/dist/chunk-P3MDLWLN.cjs.map +0 -1
  56. package/dist/chunk-X3OFGGSE.cjs.map +0 -1
  57. package/dist/chunk-Z3UPDOBY.js.map +0 -1
package/dist/thesis.d.ts CHANGED
@@ -1,20 +1,37 @@
1
- import { b as CabalSpyIDError } from './users-CDlDRuyv.js';
2
- export { A as ApiErrorBody, a as AuthenticationError, B as BadRequestError, C as CabalSpyID, c as CabalSpyIDErrorOptions, d as CabalSpyIDOptions, g as ConnectionError, i as DEFAULT_THESES_STREAM_URL, j as DeleteThesisParams, H as HandleKind, k as HeaderSource, I as InvalidResponseError, L as LatestThesesParams, p as ListThesesParams, M as MAX_THESES_STREAM_TOKENS, N as NotFoundError, O as OAuthError, P as PermissionError, q as PlanUserLimitError, r as PostThesisParams, R as RateLimitError, t as RateLimitInfo, u as ReactToThesisParams, w as ReplyToThesisParams, x as ReportThesisParams, S as SDK_VERSION, z as ServerError, 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, a5 as UserLike, a6 as UserThesesParams, a8 as VerifyWebhookOptions, aa as WebhookVerificationError, ab as handleKind, ac as hasX, ad as signWebhook, ae as verifyWebhook } from './users-CDlDRuyv.js';
1
+ import { b as CabalSpyIDError } from './users-B4r33i4O.js';
2
+ export { A as ApiErrorBody, a as AuthenticationError, B as BadRequestError, C as CabalSpyID, c as CabalSpyIDErrorOptions, d as CabalSpyIDOptions, g as ConnectionError, i as DEFAULT_THESES_STREAM_URL, j as DeleteThesisParams, H as HandleKind, k as HeaderSource, I as InvalidResponseError, L as LatestThesesParams, p as ListThesesParams, M as MAX_THESES_STREAM_TOKENS, N as NotFoundError, O as OAuthError, P as PermissionError, q as PlanUserLimitError, r as PostThesisParams, R as RateLimitError, t as RateLimitInfo, u as ReactToThesisParams, w as ReplyToThesisParams, x as ReportThesisParams, S as SDK_VERSION, z as ServerError, 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, a5 as UserLike, a6 as UserThesesParams, a8 as VerifyWebhookOptions, aa as WebhookVerificationError, ab as handleKind, ac as hasX, ad as signWebhook, ae as verifyWebhook } from './users-B4r33i4O.js';
3
3
  import { d as Chain, a3 as ThesisSort, a4 as ThesisStance, y as Thesis, o as Pagination, a6 as ThesisToken, B as ThesisCounts, a5 as ThesisSummary, F as ThesisEligibility, J as ThesisNotices, G as ThesisEmoji, _ as ThesisReactions, a2 as ThesisReportReason, Q as ThesisNotifications } from './types-UZeY9n1e.js';
4
4
  export { C as CHAINS, S as Scope, T as THESIS_EMOJIS, w as THESIS_MAX_BODY, x as THESIS_REPORT_REASONS, z as ThesisAuthor, E as ThesisCreatedData, I as ThesisLiveItem, K as ThesisNotification, N as ThesisNotificationKind, U as ThesisPnl, V as ThesisPosition, W as ThesisPositionPrivate, X as ThesisPositionPublic, Y as ThesisReactionData, Z as ThesisReactionUpdate, $ as ThesisReactor, a0 as ThesisRemoved, a1 as ThesisReplyData, ah as WebhookEvent } from './types-UZeY9n1e.js';
5
5
 
6
6
  declare const DEFAULT_THESIS_URL = "https://id.cabalspy.xyz/thesis/v1";
7
7
  declare const DEFAULT_THESIS_WS_URL = "wss://id.cabalspy.xyz/thesis/v1/ws";
8
+ /** Header that carries the instance's public client_id on every thesis API call. */
9
+ declare const CLIENT_ID_HEADER = "X-CSID-Client-Id";
8
10
  interface ThesisScopeOptions {
9
11
  /**
10
12
  * `global` (default): theses written on every platform. `instance`: only the
11
13
  * ones written on the instance named by `clientId`.
12
14
  */
13
15
  scope?: 'global' | 'instance';
14
- /** Your instance's client_id. Needed for `scope: 'instance'`; also credits posts to your app. */
16
+ /**
17
+ * Instance whose theses `scope: 'instance'` reads. Defaults to the client's
18
+ * own `clientId`; you rarely need to set it per call.
19
+ */
15
20
  clientId?: string;
16
21
  }
17
- interface ThesisClientOptions extends ThesisScopeOptions {
22
+ interface ThesisClientOptions {
23
+ /**
24
+ * Required. Your instance's PUBLIC client_id (`csid_...`), shown in the
25
+ * dashboard next to the API key. It is not a secret and not the API key. Sent
26
+ * as `X-CSID-Client-Id` on every call and as `client_id` on the live socket;
27
+ * also used for `scope: 'instance'` and credits posts to your app.
28
+ */
29
+ clientId: string;
30
+ /**
31
+ * `global` (default): theses written on every platform. `instance`: only the
32
+ * ones written on your instance.
33
+ */
34
+ scope?: 'global' | 'instance';
18
35
  /** The signed-in user's access token (scope `thesis`). Needed only for writing. */
19
36
  accessToken?: string | null;
20
37
  /** Default: https://id.cabalspy.xyz/thesis/v1 */
@@ -57,7 +74,7 @@ type ThesisMe = ThesisEligibility & {
57
74
  * Client for the public thesis API, e.g. in a browser app that builds its own
58
75
  * thesis UI.
59
76
  *
60
- * const theses = new ThesisClient({ accessToken }); // from csid:login
77
+ * const theses = new ThesisClient({ clientId: 'csid_...', accessToken }); // token from csid:login
61
78
  * const page = await theses.list('solana', mint, { sort: 'top' });
62
79
  * await theses.post('solana', mint, { body: 'Strong community, early.', stance: 'bull' });
63
80
  * const live = theses.live('solana', mint);
@@ -69,10 +86,11 @@ declare class ThesisClient {
69
86
  readonly wsUrl: string;
70
87
  readonly timeout: number;
71
88
  readonly scope?: 'global' | 'instance';
72
- readonly clientId?: string;
89
+ /** Your instance's public client_id, sent on every call. */
90
+ readonly clientId: string;
73
91
  private accessToken;
74
92
  private readonly fetchImpl;
75
- constructor(options?: ThesisClientOptions);
93
+ constructor(options: ThesisClientOptions);
76
94
  /** Sets (or with null clears) the user's access token, e.g. after sign-in or sign-out. */
77
95
  setAccessToken(token: string | null | undefined): void;
78
96
  private call;
@@ -125,8 +143,8 @@ declare class ThesisClient {
125
143
  appealed: true;
126
144
  }>;
127
145
  /**
128
- * Live updates for one token: thesis, reply, reaction, removed. With
129
- * `scope: 'instance'` (and `clientId`) only theses written on that instance.
146
+ * Live updates for one token: thesis, reply, reaction, removed. The socket URL
147
+ * carries `client_id`. With `scope: 'instance'` only theses written on your instance.
130
148
  */
131
149
  live(chain: Chain, mint: string, options?: ThesisLiveOptions & ThesisScopeOptions): ThesisLive;
132
150
  }
@@ -158,10 +176,23 @@ interface ThesisLiveEventMap {
158
176
  error: CabalSpyIDError;
159
177
  }
160
178
  type LiveHandler<T> = (payload: T) => void;
179
+ /** Close codes of the live socket for client_id checks (the close reason is the error code). */
180
+ declare const THESIS_WS_CLOSE_CODES: {
181
+ /** client_id_required or invalid_client: final. */
182
+ readonly CLIENT: 4401;
183
+ /** origin_not_allowed: final. */
184
+ readonly ORIGIN: 4403;
185
+ /** rate_limited: reconnects after 60 s. */
186
+ readonly RATE_LIMITED: 4429;
187
+ };
188
+ /** The error for a refusing close code of the live socket, or null for an ordinary close. */
189
+ declare function thesisCloseError(code: number | undefined, reason?: string): CabalSpyIDError | null;
161
190
  /**
162
191
  * Public live socket for one token. Messages arrive as `thesis`, `reply`,
163
192
  * `reaction` and `removed`; it reconnects after drops or missed heartbeats.
164
- * Code 1008 (invalid token address, too many sockets) is final.
193
+ * Codes 1008 (invalid token address, too many sockets), 4401 (client_id
194
+ * missing or invalid) and 4403 (origin not allowed) are final; after 4429
195
+ * (rate limited) it waits 60 s before reconnecting.
165
196
  */
166
197
  declare class ThesisLive {
167
198
  readonly url: string;
@@ -187,4 +218,4 @@ declare class ThesisLive {
187
218
  close(): void;
188
219
  }
189
220
 
190
- export { CabalSpyIDError, Chain, DEFAULT_THESIS_URL, DEFAULT_THESIS_WS_URL, Pagination, Thesis, ThesisClient, type ThesisClientOptions, ThesisCounts, ThesisEligibility, ThesisEmoji, ThesisLive, type ThesisLiveEventMap, type ThesisLiveOptions, type ThesisMe, ThesisNotices, ThesisNotifications, type ThesisPage, ThesisReactions, ThesisReportReason, type ThesisScopeOptions, ThesisSort, ThesisStance, ThesisSummary, ThesisToken, type ThesisViewer, type TokenThesesOptions, type TokenThesesPage };
221
+ export { CLIENT_ID_HEADER, CabalSpyIDError, Chain, DEFAULT_THESIS_URL, DEFAULT_THESIS_WS_URL, Pagination, THESIS_WS_CLOSE_CODES, Thesis, ThesisClient, type ThesisClientOptions, ThesisCounts, ThesisEligibility, ThesisEmoji, ThesisLive, type ThesisLiveEventMap, type ThesisLiveOptions, type ThesisMe, ThesisNotices, ThesisNotifications, type ThesisPage, ThesisReactions, ThesisReportReason, type ThesisScopeOptions, ThesisSort, ThesisStance, ThesisSummary, ThesisToken, type ThesisViewer, type TokenThesesOptions, type TokenThesesPage, thesisCloseError };
package/dist/thesis.js CHANGED
@@ -1,42 +1,48 @@
1
1
  import {
2
+ CLIENT_ID_HEADER,
2
3
  DEFAULT_THESIS_URL,
3
4
  DEFAULT_THESIS_WS_URL,
5
+ THESIS_WS_CLOSE_CODES,
4
6
  ThesisClient,
5
- ThesisLive
6
- } from "./chunk-3P7QK2PG.js";
7
+ ThesisLive,
8
+ thesisCloseError
9
+ } from "./chunk-Z23B5TY6.js";
7
10
  import "./chunk-CJFIOVBA.js";
8
11
  import {
9
- AuthenticationError,
10
- BadRequestError,
11
12
  CHAINS,
12
13
  CabalSpyID,
13
- CabalSpyIDError,
14
- ConnectionError,
15
14
  DEFAULT_THESES_STREAM_URL,
16
- InvalidResponseError,
17
15
  MAX_THESES_STREAM_TOKENS,
18
- NotFoundError,
19
- OAuthError,
20
- PermissionError,
21
- PlanUserLimitError,
22
- RateLimitError,
23
16
  SDK_VERSION,
24
- ServerError,
25
17
  THESIS_EMOJIS,
26
18
  THESIS_MAX_BODY,
27
19
  THESIS_REPORT_REASONS,
28
20
  ThesesStream,
29
- TokenVerificationError,
30
- WebhookVerificationError,
31
21
  handleKind,
32
22
  hasX,
33
23
  signWebhook,
34
24
  verifyWebhook
35
- } from "./chunk-3LKLY5WQ.js";
25
+ } from "./chunk-XLBO2RSL.js";
26
+ import {
27
+ AuthenticationError,
28
+ BadRequestError,
29
+ CabalSpyIDError,
30
+ ConnectionError,
31
+ InvalidResponseError,
32
+ NotFoundError,
33
+ OAuthError,
34
+ PermissionError,
35
+ PlanUserLimitError,
36
+ RateLimitError,
37
+ ServerError,
38
+ TokenVerificationError,
39
+ WebhookVerificationError
40
+ } from "./chunk-VOTYIQAY.js";
36
41
  export {
37
42
  AuthenticationError,
38
43
  BadRequestError,
39
44
  CHAINS,
45
+ CLIENT_ID_HEADER,
40
46
  CabalSpyID,
41
47
  CabalSpyIDError,
42
48
  ConnectionError,
@@ -55,6 +61,7 @@ export {
55
61
  THESIS_EMOJIS,
56
62
  THESIS_MAX_BODY,
57
63
  THESIS_REPORT_REASONS,
64
+ THESIS_WS_CLOSE_CODES,
58
65
  ThesesStream,
59
66
  ThesisClient,
60
67
  ThesisLive,
@@ -63,6 +70,7 @@ export {
63
70
  handleKind,
64
71
  hasX,
65
72
  signWebhook,
73
+ thesisCloseError,
66
74
  verifyWebhook
67
75
  };
68
76
  //# sourceMappingURL=thesis.js.map
@@ -46,10 +46,10 @@ declare class CabalSpyIDError extends Error {
46
46
  /** 400 — missing_parameter, invalid_parameter, instance_required */
47
47
  declare class BadRequestError extends CabalSpyIDError {
48
48
  }
49
- /** 401 — missing_api_key */
49
+ /** 401 — missing_api_key; thesis API: client_id_required, invalid_client */
50
50
  declare class AuthenticationError extends CabalSpyIDError {
51
51
  }
52
- /** 403 — invalid_api_key, product_not_enabled, no_instance, scope_not_allowed */
52
+ /** 403 — invalid_api_key, product_not_enabled, no_instance, scope_not_allowed; thesis API: origin_not_allowed */
53
53
  declare class PermissionError extends CabalSpyIDError {
54
54
  }
55
55
  /**
@@ -62,7 +62,7 @@ declare class PlanUserLimitError extends PermissionError {
62
62
  /** 404 — profile_not_found, wallet_not_found, instance_not_found */
63
63
  declare class NotFoundError extends CabalSpyIDError {
64
64
  }
65
- /** 429 — rate_limit_exceeded */
65
+ /** 429 — rate_limit_exceeded; thesis API: rate_limited (Retry-After 60) */
66
66
  declare class RateLimitError extends CabalSpyIDError {
67
67
  /** Seconds until the next request is allowed, taken from Retry-After. */
68
68
  readonly retryAfter: number | null;
@@ -736,7 +736,7 @@ declare function signWebhook(secret: string, id: string, timestamp: number, payl
736
736
  declare function verifyWebhook<D = Record<string, unknown>>(options: VerifyWebhookOptions): Promise<WebhookEvent<D>>;
737
737
 
738
738
  /** Internal helpers: encoding, WebCrypto access, environment. */
739
- declare const SDK_VERSION = "0.5.1";
739
+ declare const SDK_VERSION = "0.5.2";
740
740
 
741
741
  /**
742
742
  * People can sign in with X, Telegram or Google. Accounts without X have no
@@ -46,10 +46,10 @@ declare class CabalSpyIDError extends Error {
46
46
  /** 400 — missing_parameter, invalid_parameter, instance_required */
47
47
  declare class BadRequestError extends CabalSpyIDError {
48
48
  }
49
- /** 401 — missing_api_key */
49
+ /** 401 — missing_api_key; thesis API: client_id_required, invalid_client */
50
50
  declare class AuthenticationError extends CabalSpyIDError {
51
51
  }
52
- /** 403 — invalid_api_key, product_not_enabled, no_instance, scope_not_allowed */
52
+ /** 403 — invalid_api_key, product_not_enabled, no_instance, scope_not_allowed; thesis API: origin_not_allowed */
53
53
  declare class PermissionError extends CabalSpyIDError {
54
54
  }
55
55
  /**
@@ -62,7 +62,7 @@ declare class PlanUserLimitError extends PermissionError {
62
62
  /** 404 — profile_not_found, wallet_not_found, instance_not_found */
63
63
  declare class NotFoundError extends CabalSpyIDError {
64
64
  }
65
- /** 429 — rate_limit_exceeded */
65
+ /** 429 — rate_limit_exceeded; thesis API: rate_limited (Retry-After 60) */
66
66
  declare class RateLimitError extends CabalSpyIDError {
67
67
  /** Seconds until the next request is allowed, taken from Retry-After. */
68
68
  readonly retryAfter: number | null;
@@ -736,7 +736,7 @@ declare function signWebhook(secret: string, id: string, timestamp: number, payl
736
736
  declare function verifyWebhook<D = Record<string, unknown>>(options: VerifyWebhookOptions): Promise<WebhookEvent<D>>;
737
737
 
738
738
  /** Internal helpers: encoding, WebCrypto access, environment. */
739
- declare const SDK_VERSION = "0.5.1";
739
+ declare const SDK_VERSION = "0.5.2";
740
740
 
741
741
  /**
742
742
  * People can sign in with X, Telegram or Google. Accounts without X have no
package/dist/widget.cjs CHANGED
@@ -1,4 +1,8 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/widget.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+ var _chunkABFIPCBYcjs = require('./chunk-ABFIPCBY.cjs');
4
+
5
+ // src/widget.ts
2
6
  var WIDGET_SRC = "https://id.cabalspy.xyz/widget.js";
3
7
  var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
4
8
  var loaders = /* @__PURE__ */ new Map();
@@ -66,11 +70,17 @@ function loginButtonAttributes(o) {
66
70
  };
67
71
  }
68
72
  function thesisWidgetAttributes(o) {
73
+ if (typeof _optionalChain([o, 'optionalAccess', _3 => _3.clientId]) !== "string" || !o.clientId.trim()) {
74
+ throw new (0, _chunkABFIPCBYcjs.AuthenticationError)(
75
+ "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.",
76
+ { code: "client_id_required", parameter: "clientId", docs: "https://docs.cabalspy.xyz/cabalspy-id/errors#client_id_required" }
77
+ );
78
+ }
69
79
  return {
70
80
  "data-csid-thesis": "",
71
81
  "data-chain": o.chain,
72
82
  "data-mint": o.mint,
73
- "data-client-id": o.clientId,
83
+ "data-client-id": o.clientId.trim(),
74
84
  "data-theme": o.theme,
75
85
  "data-scope": o.scope,
76
86
  "data-sort": o.sort,
@@ -95,16 +105,16 @@ function onWidgetEvents(handlers, clientId) {
95
105
  if (!isBrowser()) return () => void 0;
96
106
  const login = (e) => {
97
107
  const d = e.detail;
98
- const aud = _optionalChain([d, 'optionalAccess', _3 => _3.idToken]) ? audOf(d.idToken) : void 0;
108
+ const aud = _optionalChain([d, 'optionalAccess', _4 => _4.idToken]) ? audOf(d.idToken) : void 0;
99
109
  if (clientId && aud !== void 0 && aud !== clientId) return;
100
- _optionalChain([handlers, 'access', _4 => _4.onLogin, 'optionalCall', _5 => _5(d)]);
110
+ _optionalChain([handlers, 'access', _5 => _5.onLogin, 'optionalCall', _6 => _6(d)]);
101
111
  };
102
112
  const logout = (e) => {
103
113
  const d = e.detail;
104
- if (clientId && _optionalChain([d, 'optionalAccess', _6 => _6.clientId]) && d.clientId !== clientId) return;
105
- _optionalChain([handlers, 'access', _7 => _7.onLogout, 'optionalCall', _8 => _8(d)]);
114
+ if (clientId && _optionalChain([d, 'optionalAccess', _7 => _7.clientId]) && d.clientId !== clientId) return;
115
+ _optionalChain([handlers, 'access', _8 => _8.onLogout, 'optionalCall', _9 => _9(d)]);
106
116
  };
107
- const error = (e) => _optionalChain([handlers, 'access', _9 => _9.onError, 'optionalCall', _10 => _10(e.detail)]);
117
+ const error = (e) => _optionalChain([handlers, 'access', _10 => _10.onError, 'optionalCall', _11 => _11(e.detail)]);
108
118
  window.addEventListener("csid:login", login);
109
119
  window.addEventListener("csid:logout", logout);
110
120
  window.addEventListener("csid:error", error);
@@ -140,7 +150,7 @@ function mountWith(target, options, attributes, after, scriptSrc, onLoadError) {
140
150
  after(csid, current);
141
151
  }
142
152
  },
143
- (err) => _optionalChain([onLoadError, 'optionalCall', _11 => _11(err)])
153
+ (err) => _optionalChain([onLoadError, 'optionalCall', _12 => _12(err)])
144
154
  );
145
155
  };
146
156
  render();
@@ -165,7 +175,7 @@ function mountLoginButton(target, options) {
165
175
  loginButtonAttributes,
166
176
  () => void 0,
167
177
  options.scriptSrc,
168
- (err) => _optionalChain([options, 'access', _12 => _12.onError, 'optionalCall', _13 => _13({ error: "widget_load_failed", description: err.message })])
178
+ (err) => _optionalChain([options, 'access', _13 => _13.onError, 'optionalCall', _14 => _14({ error: "widget_load_failed", description: err.message })])
169
179
  );
170
180
  return {
171
181
  get element() {
@@ -184,14 +194,19 @@ function mountTheses(target, options) {
184
194
  options,
185
195
  thesisWidgetAttributes,
186
196
  (csid) => {
187
- if (options.accessToken !== void 0) _optionalChain([csid, 'access', _14 => _14.thesis, 'optionalAccess', _15 => _15.setAccessToken, 'call', _16 => _16(options.accessToken)]);
197
+ if (options.accessToken !== void 0) _optionalChain([csid, 'access', _15 => _15.thesis, 'optionalAccess', _16 => _16.setAccessToken, 'call', _17 => _17(options.accessToken)]);
188
198
  },
189
199
  options.scriptSrc
190
200
  );
191
201
  }
192
202
  async function setThesisAccessToken(token, scriptSrc) {
193
203
  const csid = await loadCabalSpyIDWidget(scriptSrc);
194
- _optionalChain([csid, 'access', _17 => _17.thesis, 'optionalAccess', _18 => _18.setAccessToken, 'call', _19 => _19(token)]);
204
+ _optionalChain([csid, 'access', _18 => _18.thesis, 'optionalAccess', _19 => _19.setAccessToken, 'call', _20 => _20(token)]);
205
+ }
206
+ async function setThesisSignInHandler(handler, scriptSrc) {
207
+ if (!isBrowser()) return;
208
+ const csid = await loadCabalSpyIDWidget(scriptSrc);
209
+ _optionalChain([csid, 'access', _21 => _21.thesis, 'optionalAccess', _22 => _22.setSignInHandler, 'optionalCall', _23 => _23(handler)]);
195
210
  }
196
211
  async function startLogin(options, scriptSrc) {
197
212
  const csid = await loadCabalSpyIDWidget(scriptSrc);
@@ -213,5 +228,6 @@ async function startLogin(options, scriptSrc) {
213
228
 
214
229
 
215
230
 
216
- exports.WIDGET_SRC = WIDGET_SRC; exports.audOf = audOf; exports.isBrowser = isBrowser; exports.loadCabalSpyIDWidget = loadCabalSpyIDWidget; exports.loginButtonAttributes = loginButtonAttributes; exports.mountLoginButton = mountLoginButton; exports.mountTheses = mountTheses; exports.onWidgetEvents = onWidgetEvents; exports.setThesisAccessToken = setThesisAccessToken; exports.startLogin = startLogin; exports.thesisWidgetAttributes = thesisWidgetAttributes;
231
+
232
+ exports.WIDGET_SRC = WIDGET_SRC; exports.audOf = audOf; exports.isBrowser = isBrowser; exports.loadCabalSpyIDWidget = loadCabalSpyIDWidget; exports.loginButtonAttributes = loginButtonAttributes; exports.mountLoginButton = mountLoginButton; exports.mountTheses = mountTheses; exports.onWidgetEvents = onWidgetEvents; exports.setThesisAccessToken = setThesisAccessToken; exports.setThesisSignInHandler = setThesisSignInHandler; exports.startLogin = startLogin; exports.thesisWidgetAttributes = thesisWidgetAttributes;
217
233
  //# sourceMappingURL=widget.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["c:\\Users\\davis\\Desktop\\cabalspy-web\\github\\cabalspy-id-js\\dist\\widget.cjs"],"names":[],"mappings":"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AACA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,QAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,MAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA,EAAA;AACA,kBAAA;AACA;AACA;AACA,EAAA;AACA,EAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,EAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"C:\\Users\\davis\\Desktop\\cabalspy-web\\github\\cabalspy-id-js\\dist\\widget.cjs","sourcesContent":[null]}
1
+ {"version":3,"sources":["/home/runner/work/cabalspy-id-js/cabalspy-id-js/dist/widget.cjs","../src/widget.ts"],"names":[],"mappings":"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","file":"/home/runner/work/cabalspy-id-js/cabalspy-id-js/dist/widget.cjs","sourcesContent":[null,"/**\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"]}
package/dist/widget.d.cts CHANGED
@@ -57,9 +57,22 @@ interface CSIDGlobal {
57
57
  thesis?: {
58
58
  /** Lets thesis widgets write as this user (access token with the scope "thesis"). */
59
59
  setAccessToken(token: string | null): void;
60
+ /**
61
+ * Your own sign-in for the widget's "Sign in to write" (null removes it).
62
+ * See {@link setThesisSignInHandler}.
63
+ */
64
+ setSignInHandler?(handler: ThesisSignInHandler | null): void;
60
65
  clear(): void;
61
66
  };
62
67
  }
68
+ /**
69
+ * Your page's sign-in for thesis widgets: obtain a thesis-capable access token
70
+ * for the person (e.g. your platform's own CabalSpy ID access token with the
71
+ * scope "thesis", from your backend), call `setThesisAccessToken(token)` (or
72
+ * `CSID.thesis.setAccessToken(token)`) and resolve `true`; resolve `false` when
73
+ * the person is not signed in.
74
+ */
75
+ type ThesisSignInHandler = () => Promise<boolean> | boolean;
63
76
  declare global {
64
77
  interface Window {
65
78
  CSID?: CSIDGlobal;
@@ -107,13 +120,15 @@ interface ThesisWidgetOptions {
107
120
  /** Token mint / contract address. */
108
121
  mint: string;
109
122
  /**
110
- * Your instance's client_id: loads the design saved in the dashboard (the
123
+ * Required. Your instance's PUBLIC client_id (`csid_...`, shown in the
124
+ * dashboard next to the API key; not the API key). The widget sends it on
125
+ * every thesis API call; it also loads the design saved in the dashboard (the
111
126
  * options below override it), credits posts to your app and enables `scope: 'instance'`.
112
127
  */
113
- clientId?: string;
128
+ clientId: string;
114
129
  /** Default: dark */
115
130
  theme?: 'dark' | 'light';
116
- /** `global` (default): theses from every platform. `instance`: only the ones written on your instance (needs clientId). */
131
+ /** `global` (default): theses from every platform. `instance`: only the ones written on your instance. */
117
132
  scope?: 'global' | 'instance';
118
133
  /** Initial order. Default: new */
119
134
  sort?: 'new' | 'top';
@@ -142,7 +157,7 @@ interface ThesisWidgetOptions {
142
157
  }
143
158
  /** The widget's data-* attributes for a login button. */
144
159
  declare function loginButtonAttributes(o: LoginButtonOptions): Record<string, string | undefined>;
145
- /** The widget's data-* attributes for a theses widget. */
160
+ /** The widget's data-* attributes for a theses widget. Throws `client_id_required` without `clientId`. */
146
161
  declare function thesisWidgetAttributes(o: ThesisWidgetOptions): Record<string, string | undefined>;
147
162
  interface WidgetEventHandlers {
148
163
  /** A browser-app login finished (window event `csid:login`). */
@@ -174,7 +189,8 @@ declare function mountLoginButton(target: Element | string, options: LoginButton
174
189
  }): MountedWidget<LoginButtonOptions>;
175
190
  /**
176
191
  * Mounts the token theses widget into `target` (an element or a CSS selector).
177
- * `accessToken` (scope `thesis`) lets the signed-in person write right away.
192
+ * `clientId` (your instance's public client_id) is required. `accessToken`
193
+ * (scope `thesis`) lets the signed-in person write right away.
178
194
  */
179
195
  declare function mountTheses(target: Element | string, options: ThesisWidgetOptions & {
180
196
  accessToken?: string | null;
@@ -185,6 +201,21 @@ declare function mountTheses(target: Element | string, options: ThesisWidgetOpti
185
201
  * scope `thesis`), or signs them out with `null`.
186
202
  */
187
203
  declare function setThesisAccessToken(token: string | null, scriptSrc?: string): Promise<void>;
204
+ /**
205
+ * Lets your page handle the thesis widgets' "Sign in to write": the widget
206
+ * calls `handler` instead of the page's login button / popup. While a handler
207
+ * is set, the widget treats the page as the source of truth for sign-in (signed
208
+ * out on the page = signed out in the widget). `null` removes it. Waits for
209
+ * widget.js when it is not loaded yet; a no-op on the server.
210
+ *
211
+ * setThesisSignInHandler(async () => {
212
+ * const token = await fetch('/api/csid-thesis-token').then((r) => (r.ok ? r.text() : null));
213
+ * if (!token) return false;
214
+ * await setThesisAccessToken(token);
215
+ * return true;
216
+ * });
217
+ */
218
+ declare function setThesisSignInHandler(handler: ThesisSignInHandler | null, scriptSrc?: string): Promise<void>;
188
219
  /** Starts the browser (public client) login flow. */
189
220
  declare function startLogin(options: {
190
221
  clientId: string;
@@ -192,4 +223,4 @@ declare function startLogin(options: {
192
223
  scope?: string | OidcScope[];
193
224
  }, scriptSrc?: string): Promise<void>;
194
225
 
195
- export { type CSIDGlobal, type CabalSpyIDErrorEvent, type CabalSpyIDLogin, type CabalSpyIDLogoutEvent, type CabalSpyIDUserSummary, type LoginButtonOptions, type MountedWidget, type ThesisWidgetOptions, WIDGET_SRC, type WidgetEventHandlers, audOf, isBrowser, loadCabalSpyIDWidget, loginButtonAttributes, mountLoginButton, mountTheses, onWidgetEvents, setThesisAccessToken, startLogin, thesisWidgetAttributes };
226
+ export { type CSIDGlobal, type CabalSpyIDErrorEvent, type CabalSpyIDLogin, type CabalSpyIDLogoutEvent, type CabalSpyIDUserSummary, type LoginButtonOptions, type MountedWidget, type ThesisSignInHandler, type ThesisWidgetOptions, WIDGET_SRC, type WidgetEventHandlers, audOf, isBrowser, loadCabalSpyIDWidget, loginButtonAttributes, mountLoginButton, mountTheses, onWidgetEvents, setThesisAccessToken, setThesisSignInHandler, startLogin, thesisWidgetAttributes };
package/dist/widget.d.ts CHANGED
@@ -57,9 +57,22 @@ interface CSIDGlobal {
57
57
  thesis?: {
58
58
  /** Lets thesis widgets write as this user (access token with the scope "thesis"). */
59
59
  setAccessToken(token: string | null): void;
60
+ /**
61
+ * Your own sign-in for the widget's "Sign in to write" (null removes it).
62
+ * See {@link setThesisSignInHandler}.
63
+ */
64
+ setSignInHandler?(handler: ThesisSignInHandler | null): void;
60
65
  clear(): void;
61
66
  };
62
67
  }
68
+ /**
69
+ * Your page's sign-in for thesis widgets: obtain a thesis-capable access token
70
+ * for the person (e.g. your platform's own CabalSpy ID access token with the
71
+ * scope "thesis", from your backend), call `setThesisAccessToken(token)` (or
72
+ * `CSID.thesis.setAccessToken(token)`) and resolve `true`; resolve `false` when
73
+ * the person is not signed in.
74
+ */
75
+ type ThesisSignInHandler = () => Promise<boolean> | boolean;
63
76
  declare global {
64
77
  interface Window {
65
78
  CSID?: CSIDGlobal;
@@ -107,13 +120,15 @@ interface ThesisWidgetOptions {
107
120
  /** Token mint / contract address. */
108
121
  mint: string;
109
122
  /**
110
- * Your instance's client_id: loads the design saved in the dashboard (the
123
+ * Required. Your instance's PUBLIC client_id (`csid_...`, shown in the
124
+ * dashboard next to the API key; not the API key). The widget sends it on
125
+ * every thesis API call; it also loads the design saved in the dashboard (the
111
126
  * options below override it), credits posts to your app and enables `scope: 'instance'`.
112
127
  */
113
- clientId?: string;
128
+ clientId: string;
114
129
  /** Default: dark */
115
130
  theme?: 'dark' | 'light';
116
- /** `global` (default): theses from every platform. `instance`: only the ones written on your instance (needs clientId). */
131
+ /** `global` (default): theses from every platform. `instance`: only the ones written on your instance. */
117
132
  scope?: 'global' | 'instance';
118
133
  /** Initial order. Default: new */
119
134
  sort?: 'new' | 'top';
@@ -142,7 +157,7 @@ interface ThesisWidgetOptions {
142
157
  }
143
158
  /** The widget's data-* attributes for a login button. */
144
159
  declare function loginButtonAttributes(o: LoginButtonOptions): Record<string, string | undefined>;
145
- /** The widget's data-* attributes for a theses widget. */
160
+ /** The widget's data-* attributes for a theses widget. Throws `client_id_required` without `clientId`. */
146
161
  declare function thesisWidgetAttributes(o: ThesisWidgetOptions): Record<string, string | undefined>;
147
162
  interface WidgetEventHandlers {
148
163
  /** A browser-app login finished (window event `csid:login`). */
@@ -174,7 +189,8 @@ declare function mountLoginButton(target: Element | string, options: LoginButton
174
189
  }): MountedWidget<LoginButtonOptions>;
175
190
  /**
176
191
  * Mounts the token theses widget into `target` (an element or a CSS selector).
177
- * `accessToken` (scope `thesis`) lets the signed-in person write right away.
192
+ * `clientId` (your instance's public client_id) is required. `accessToken`
193
+ * (scope `thesis`) lets the signed-in person write right away.
178
194
  */
179
195
  declare function mountTheses(target: Element | string, options: ThesisWidgetOptions & {
180
196
  accessToken?: string | null;
@@ -185,6 +201,21 @@ declare function mountTheses(target: Element | string, options: ThesisWidgetOpti
185
201
  * scope `thesis`), or signs them out with `null`.
186
202
  */
187
203
  declare function setThesisAccessToken(token: string | null, scriptSrc?: string): Promise<void>;
204
+ /**
205
+ * Lets your page handle the thesis widgets' "Sign in to write": the widget
206
+ * calls `handler` instead of the page's login button / popup. While a handler
207
+ * is set, the widget treats the page as the source of truth for sign-in (signed
208
+ * out on the page = signed out in the widget). `null` removes it. Waits for
209
+ * widget.js when it is not loaded yet; a no-op on the server.
210
+ *
211
+ * setThesisSignInHandler(async () => {
212
+ * const token = await fetch('/api/csid-thesis-token').then((r) => (r.ok ? r.text() : null));
213
+ * if (!token) return false;
214
+ * await setThesisAccessToken(token);
215
+ * return true;
216
+ * });
217
+ */
218
+ declare function setThesisSignInHandler(handler: ThesisSignInHandler | null, scriptSrc?: string): Promise<void>;
188
219
  /** Starts the browser (public client) login flow. */
189
220
  declare function startLogin(options: {
190
221
  clientId: string;
@@ -192,4 +223,4 @@ declare function startLogin(options: {
192
223
  scope?: string | OidcScope[];
193
224
  }, scriptSrc?: string): Promise<void>;
194
225
 
195
- export { type CSIDGlobal, type CabalSpyIDErrorEvent, type CabalSpyIDLogin, type CabalSpyIDLogoutEvent, type CabalSpyIDUserSummary, type LoginButtonOptions, type MountedWidget, type ThesisWidgetOptions, WIDGET_SRC, type WidgetEventHandlers, audOf, isBrowser, loadCabalSpyIDWidget, loginButtonAttributes, mountLoginButton, mountTheses, onWidgetEvents, setThesisAccessToken, startLogin, thesisWidgetAttributes };
226
+ export { type CSIDGlobal, type CabalSpyIDErrorEvent, type CabalSpyIDLogin, type CabalSpyIDLogoutEvent, type CabalSpyIDUserSummary, type LoginButtonOptions, type MountedWidget, type ThesisSignInHandler, type ThesisWidgetOptions, WIDGET_SRC, type WidgetEventHandlers, audOf, isBrowser, loadCabalSpyIDWidget, loginButtonAttributes, mountLoginButton, mountTheses, onWidgetEvents, setThesisAccessToken, setThesisSignInHandler, startLogin, thesisWidgetAttributes };
package/dist/widget.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ AuthenticationError
3
+ } from "./chunk-VOTYIQAY.js";
4
+
1
5
  // src/widget.ts
2
6
  var WIDGET_SRC = "https://id.cabalspy.xyz/widget.js";
3
7
  var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
@@ -66,11 +70,17 @@ function loginButtonAttributes(o) {
66
70
  };
67
71
  }
68
72
  function thesisWidgetAttributes(o) {
73
+ if (typeof o?.clientId !== "string" || !o.clientId.trim()) {
74
+ throw new AuthenticationError(
75
+ "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.",
76
+ { code: "client_id_required", parameter: "clientId", docs: "https://docs.cabalspy.xyz/cabalspy-id/errors#client_id_required" }
77
+ );
78
+ }
69
79
  return {
70
80
  "data-csid-thesis": "",
71
81
  "data-chain": o.chain,
72
82
  "data-mint": o.mint,
73
- "data-client-id": o.clientId,
83
+ "data-client-id": o.clientId.trim(),
74
84
  "data-theme": o.theme,
75
85
  "data-scope": o.scope,
76
86
  "data-sort": o.sort,
@@ -193,6 +203,11 @@ async function setThesisAccessToken(token, scriptSrc) {
193
203
  const csid = await loadCabalSpyIDWidget(scriptSrc);
194
204
  csid.thesis?.setAccessToken(token);
195
205
  }
206
+ async function setThesisSignInHandler(handler, scriptSrc) {
207
+ if (!isBrowser()) return;
208
+ const csid = await loadCabalSpyIDWidget(scriptSrc);
209
+ csid.thesis?.setSignInHandler?.(handler);
210
+ }
196
211
  async function startLogin(options, scriptSrc) {
197
212
  const csid = await loadCabalSpyIDWidget(scriptSrc);
198
213
  await csid.login({
@@ -211,6 +226,7 @@ export {
211
226
  mountTheses,
212
227
  onWidgetEvents,
213
228
  setThesisAccessToken,
229
+ setThesisSignInHandler,
214
230
  startLogin,
215
231
  thesisWidgetAttributes
216
232
  };