@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 +65 -10
- package/dist/index.cjs +64 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +60 -4
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
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
|
|
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
|
|
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,
|
|
300
|
-
3.
|
|
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
|
|
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,
|
|
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,
|
|
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
|