@sylergydigital/issue-pin-sdk 0.6.2 → 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
@@ -356,6 +356,54 @@ For bulk provisioning or non-Supabase apps, use the `federate-user` edge functio
356
356
 
357
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.
358
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
+
359
407
  ## AI Agent Prompt
360
408
 
361
409
  Paste this into **Claude Code**, **Cursor**, or **Codex** to integrate the SDK automatically:
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
@@ -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]);
@@ -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