@sylergydigital/issue-pin-sdk 0.6.1 → 0.6.3

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/README.md CHANGED
@@ -15,10 +15,16 @@ npm install @sylergydigital/issue-pin-sdk
15
15
  Ensure these are installed in your app:
16
16
 
17
17
  ```bash
18
- npm install react react-dom @supabase/supabase-js html2canvas lucide-react
18
+ npm install react react-dom @supabase/supabase-js html2canvas-pro lucide-react
19
19
  ```
20
20
 
21
- The public GitHub repo at `sylergydigital/issue-pin-sdk` is a read-only mirror. Source changes should be made in the private source repo and will sync across automatically.
21
+ The public GitHub repo at `https://github.com/sylergydigital/issue-pin-sdk` is a read-only mirror of the publishable SDK package.
22
+
23
+ The package is published publicly on npm, so no `.npmrc` or GitHub Packages token setup is required for installation.
24
+
25
+ npm package page: `https://www.npmjs.com/package/@sylergydigital/issue-pin-sdk`
26
+
27
+ Source changes should still be made in the private source repo and will sync across automatically.
22
28
 
23
29
  `react-router-dom` is **not** required. The SDK tracks `window.location` for thread fetching and `?highlight_thread=` without router context.
24
30
 
@@ -290,14 +296,15 @@ import { supabase } from "./lib/supabase";
290
296
  <IssuePin apiKey="ew_live_..." supabaseClient={supabase} enabled={feedbackOn} />
291
297
  ```
292
298
 
293
- No backend code, edge functions, or sync secrets needed. The SDK handles everything transparently.
299
+ No backend code or sync secrets are needed in the host app. On the first successful federation call, Issue Pin verifies the external Supabase JWT against its issuer JWKS and bootstraps the workspace trust mapping automatically.
294
300
 
295
301
  ### How auto-federation works
296
302
 
297
303
  When a user is detected via `supabaseClient`:
298
304
  1. The SDK calls the `sdk-federate` endpoint with the API key + user identity
299
- 2. The endpoint validates the API key, resolves the workspace, and upserts the user as a `client_federated` workspace member with `commenter` role
300
- 3. An identity source (`sdk:<workspace-slug>`) is auto-created if it doesn't exist
305
+ 2. The endpoint validates the API key, resolves the workspace, verifies the external Supabase JWT against the issuer JWKS, and derives the external project ref from the token issuer
306
+ 3. If the workspace has no active Supabase issuer mapping yet, Issue Pin auto-creates the matching `identity_sources` and `workspace_identity_sources` records for that issuer
307
+ 4. The endpoint upserts the user as a `client_federated` workspace member with `commenter` role
301
308
 
302
309
  ```mermaid
303
310
  sequenceDiagram
@@ -311,6 +318,8 @@ sequenceDiagram
311
318
  SDK->>EF: POST { apiKey, externalId, email, displayName }
312
319
  EF->>DB: SHA-256(apiKey) → resolve_api_key()
313
320
  DB-->>EF: workspace_id
321
+ EF->>DB: verify JWT via issuer JWKS
322
+ EF->>DB: bootstrap issuer mapping if missing
314
323
  EF->>DB: Upsert user_identities
315
324
  EF->>DB: Upsert workspace_members (commenter role)
316
325
  DB-->>EF: OK
@@ -319,6 +328,8 @@ sequenceDiagram
319
328
  Note over SDK: Result cached — runs once per session
320
329
  ```
321
330
 
331
+ The first verified Supabase issuer becomes the workspace's default SDK federation issuer. If you later need to trust a different or additional issuer, add it explicitly in the Federation UI.
332
+
322
333
  ### Other auth providers (manual)
323
334
 
324
335
  For non-Supabase auth systems, pass identity props directly:
@@ -345,6 +356,54 @@ For bulk provisioning or non-Supabase apps, use the `federate-user` edge functio
345
356
 
346
357
  The API key is a **publishable key** — safe to include in client-side code. It only grants scoped access to create threads and comments for the associated workspace, enforced server-side via Row Level Security.
347
358
 
359
+ ## Content Security Policy (CSP)
360
+
361
+ If your app sets a Content Security Policy header, add these directives so the IssuePin SDK can connect to Supabase, render screenshot previews, and apply inline styles:
362
+
363
+ ```
364
+ connect-src https://*.supabase.co wss://*.supabase.co;
365
+ img-src data: blob:;
366
+ style-src 'unsafe-inline';
367
+ ```
368
+
369
+ Merge these with your existing CSP directives — for example, if you already have `connect-src 'self'`, change it to `connect-src 'self' https://*.supabase.co wss://*.supabase.co;`.
370
+
371
+ > **Narrower alternative:** Replace `*.supabase.co` with your project's specific URL (e.g. `https://abcdef.supabase.co`) for a tighter policy.
372
+
373
+ ### Why each directive is needed
374
+
375
+ | Directive | Sources | Reason |
376
+ |-----------|---------|--------|
377
+ | `connect-src` | `https://*.supabase.co` | REST API calls to read/write threads and comments |
378
+ | `connect-src` | `wss://*.supabase.co` | WebSocket connections for real-time thread updates |
379
+ | `img-src` | `data:` `blob:` | Screenshot preview thumbnails rendered as data URIs and blob URLs |
380
+ | `style-src` | `'unsafe-inline'` | html2canvas-pro injects inline styles during screenshot capture |
381
+
382
+ ### Programmatic access
383
+
384
+ The SDK exports a typed constant for use in server-side middleware or build-time CSP generation:
385
+
386
+ ```typescript
387
+ import { CSP_REQUIREMENTS } from "@sylergydigital/issue-pin-sdk";
388
+
389
+ // CSP_REQUIREMENTS = {
390
+ // "connect-src": ["https://*.supabase.co", "wss://*.supabase.co"],
391
+ // "img-src": ["data:", "blob:"],
392
+ // "style-src": ["'unsafe-inline'"],
393
+ // }
394
+ ```
395
+
396
+ ### Runtime detection
397
+
398
+ When a CSP policy blocks an SDK operation, the browser console shows a warning like:
399
+
400
+ ```
401
+ [IssuePin] CSP violation: connect-src blocked "https://abc.supabase.co/rest/v1/threads".
402
+ Add to your Content-Security-Policy: connect-src https://*.supabase.co wss://*.supabase.co
403
+ ```
404
+
405
+ These warnings appear automatically — no `debug` flag needed.
406
+
348
407
  ## AI Agent Prompt
349
408
 
350
409
  Paste this into **Claude Code**, **Cursor**, or **Codex** to integrate the SDK automatically:
@@ -353,11 +412,7 @@ Paste this into **Claude Code**, **Cursor**, or **Codex** to integrate the SDK a
353
412
  Add the Issue Pin feedback SDK to this React app.
354
413
 
355
414
  Install: npm install @sylergydigital/issue-pin-sdk
356
- Peer deps: npm install @supabase/supabase-js html2canvas lucide-react
357
-
358
- .npmrc setup (required for GitHub Packages):
359
- //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
360
- @sylergydigital:registry=https://npm.pkg.github.com
415
+ Peer deps: npm install react react-dom @supabase/supabase-js html2canvas-pro lucide-react
361
416
 
362
417
  Integration:
363
418
  1. Import { IssuePin } from "@sylergydigital/issue-pin-sdk"
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ CSP_REQUIREMENTS: () => CSP_REQUIREMENTS,
33
34
  ElementWhisperer: () => IssuePin,
34
35
  FeedbackButton: () => FeedbackButton,
35
36
  FeedbackOverlay: () => FeedbackOverlay,
@@ -40,6 +41,7 @@ __export(index_exports, {
40
41
  SdkCommentPopover: () => SdkCommentPopover,
41
42
  ThreadPins: () => ThreadPins,
42
43
  Z: () => Z,
44
+ normalizeUserId: () => normalizeUserId,
43
45
  useFeedback: () => useFeedback,
44
46
  useFeedbackSafe: () => useFeedbackSafe,
45
47
  useIssuePinAnchor: () => useIssuePinAnchor
@@ -52,7 +54,7 @@ var import_react11 = require("react");
52
54
  // src/FeedbackProvider.tsx
53
55
  var import_react2 = require("react");
54
56
  var import_supabase_js = require("@supabase/supabase-js");
55
- var import_html2canvas = __toESM(require("html2canvas"), 1);
57
+ var import_html2canvas_pro = __toESM(require("html2canvas-pro"), 1);
56
58
 
57
59
  // src/useDocumentPathname.ts
58
60
  var import_react = require("react");
@@ -125,6 +127,51 @@ var T = {
125
127
  ring: "#6366f1"
126
128
  };
127
129
 
130
+ // src/uuid.ts
131
+ var import_uuid = require("uuid");
132
+ var NAMESPACE = "8920e263-ef53-4df0-8108-968cd5d8939e";
133
+ var UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
134
+ function normalizeUserId(userId) {
135
+ if (UUID_SHAPE.test(userId)) return userId;
136
+ return (0, import_uuid.v5)(userId, NAMESPACE);
137
+ }
138
+
139
+ // src/csp.ts
140
+ var CSP_REQUIREMENTS = {
141
+ "connect-src": ["https://*.supabase.co", "wss://*.supabase.co"],
142
+ "img-src": ["data:", "blob:"],
143
+ "style-src": ["'unsafe-inline'"]
144
+ };
145
+ var warnedDirectives = /* @__PURE__ */ new Set();
146
+ function isIssuePinViolation(event) {
147
+ const blocked = event.blockedURI;
148
+ const directive = event.violatedDirective;
149
+ if (directive.startsWith("connect-src") && blocked.includes("supabase.co")) return true;
150
+ if (directive.startsWith("img-src") && (blocked === "data" || blocked === "blob")) return true;
151
+ if (directive.startsWith("style-src") && (blocked === "inline" || blocked === "'unsafe-inline'")) return true;
152
+ return false;
153
+ }
154
+ function handleViolation(event) {
155
+ if (!isIssuePinViolation(event)) return;
156
+ const directive = event.violatedDirective.split(" ")[0];
157
+ if (warnedDirectives.has(directive)) return;
158
+ warnedDirectives.add(directive);
159
+ const sources = CSP_REQUIREMENTS[directive];
160
+ const fix = sources ? sources.join(" ") : event.blockedURI;
161
+ console.warn(
162
+ `[IssuePin] CSP violation: ${directive} blocked "${event.blockedURI}". Add to your Content-Security-Policy: ${directive} ${fix}`
163
+ );
164
+ }
165
+ function subscribeCspDetection() {
166
+ if (typeof document === "undefined") return () => {
167
+ };
168
+ const handler = (e) => handleViolation(e);
169
+ document.addEventListener("securitypolicyviolation", handler);
170
+ return () => {
171
+ document.removeEventListener("securitypolicyviolation", handler);
172
+ };
173
+ }
174
+
128
175
  // src/FeedbackProvider.tsx
129
176
  var import_jsx_runtime = require("react/jsx-runtime");
130
177
  var FeedbackContext = (0, import_react2.createContext)(null);
@@ -176,11 +223,11 @@ function setThreadsSchemaMode(workspaceId, mode) {
176
223
  threadsSchemaModeByWorkspace.set(workspaceId, mode);
177
224
  }
178
225
  function resolveIssuePinActorUserId(opts) {
179
- if (opts.explicitUserId) return opts.explicitUserId;
226
+ if (opts.explicitUserId) return normalizeUserId(opts.explicitUserId);
180
227
  if (opts.hasApiKey && !opts.skipFederation && opts.autoIdentityUserId) {
181
228
  return opts.federatedLocalUserId;
182
229
  }
183
- return opts.autoIdentityUserId;
230
+ return opts.autoIdentityUserId ? normalizeUserId(opts.autoIdentityUserId) : opts.autoIdentityUserId;
184
231
  }
185
232
  function resolveIssuePinActorReady(opts) {
186
233
  if (!opts.authReady) return false;
@@ -417,6 +464,10 @@ function FeedbackProvider({
417
464
  hasApiKey: !!config.apiKey,
418
465
  skipFederation: config.skipFederation
419
466
  });
467
+ const rawUserId = config.userId ?? autoIdentity.userId;
468
+ if (config.debug && rawUserId && effectiveUserId && rawUserId !== effectiveUserId) {
469
+ console.log(`[EW SDK] User ID normalized: "${rawUserId}" -> "${effectiveUserId}"`);
470
+ }
420
471
  const actorReady = resolveIssuePinActorReady({
421
472
  authReady,
422
473
  explicitUserId: config.userId,
@@ -501,12 +552,13 @@ function FeedbackProviderInner({
501
552
  console.log(`[EW SDK] ${message}`, extra);
502
553
  }, [debug]);
503
554
  (0, import_react2.useEffect)(() => {
555
+ if (!authReady) return;
504
556
  if (!userDisplayName && !userEmail) {
505
557
  console.warn(
506
558
  '[IssuePin] No user identity provided \u2014 comments will appear as "Unknown". Pass supabaseClient={supabase} for automatic identity, or user={{ email, displayName }} for manual attribution. See https://github.com/sylergydigital/issue-pin/blob/main/sdk/README.md#user-identity-avoiding-unknown-comments'
507
559
  );
508
560
  }
509
- }, []);
561
+ }, [authReady, userDisplayName, userEmail]);
510
562
  const client = (0, import_react2.useMemo)(() => {
511
563
  try {
512
564
  return (0, import_supabase_js.createClient)(supabaseUrl, supabaseAnonKey, {
@@ -613,6 +665,10 @@ function FeedbackProviderInner({
613
665
  debugLog("FeedbackProvider unmounted", { workspaceId, path: pathname });
614
666
  };
615
667
  }, [debugLog, workspaceId, pathname]);
668
+ (0, import_react2.useEffect)(() => {
669
+ const unsubscribe = subscribeCspDetection();
670
+ return unsubscribe;
671
+ }, []);
616
672
  (0, import_react2.useEffect)(() => {
617
673
  debugLog("mode / feedbackActive", { mode, feedbackActive });
618
674
  }, [debugLog, mode, feedbackActive]);
@@ -680,7 +736,7 @@ function FeedbackProviderInner({
680
736
  if (!client) return;
681
737
  try {
682
738
  await new Promise((resolve) => setTimeout(resolve, 300));
683
- const canvas = await (0, import_html2canvas.default)(document.body, {
739
+ const canvas = await (0, import_html2canvas_pro.default)(document.body, {
684
740
  useCORS: true,
685
741
  allowTaint: true,
686
742
  logging: false,
@@ -712,7 +768,7 @@ function FeedbackProviderInner({
712
768
  setMenuOpen(false);
713
769
  setScreenshotCapturing(true);
714
770
  try {
715
- const canvas = await (0, import_html2canvas.default)(document.body, {
771
+ const canvas = await (0, import_html2canvas_pro.default)(document.body, {
716
772
  useCORS: true,
717
773
  scale: window.devicePixelRatio,
718
774
  logging: false,
@@ -2483,6 +2539,7 @@ function PinsSlot() {
2483
2539
  }
2484
2540
  // Annotate the CommonJS export names for ESM import in node:
2485
2541
  0 && (module.exports = {
2542
+ CSP_REQUIREMENTS,
2486
2543
  ElementWhisperer,
2487
2544
  FeedbackButton,
2488
2545
  FeedbackOverlay,
@@ -2493,6 +2550,7 @@ function PinsSlot() {
2493
2550
  SdkCommentPopover,
2494
2551
  ThreadPins,
2495
2552
  Z,
2553
+ normalizeUserId,
2496
2554
  useFeedback,
2497
2555
  useFeedbackSafe,
2498
2556
  useIssuePinAnchor