@mindbowser_inc/ui-feedback-plugin 0.3.2 → 0.4.0
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 +8 -0
- package/dist/aws-DieAIoX4.mjs +134 -0
- package/dist/{aws-CkWYvUhs.mjs.map → aws-DieAIoX4.mjs.map} +1 -1
- package/dist/{index-D6dCjvzy.mjs → index-BcqBbswO.mjs} +181 -147
- package/dist/index-BcqBbswO.mjs.map +1 -0
- package/dist/{supabase-CbB80UVb.mjs → supabase-BUHP2OkK.mjs} +2 -2
- package/dist/{supabase-CbB80UVb.mjs.map → supabase-BUHP2OkK.mjs.map} +1 -1
- package/dist/ui-feedback-plugin.es.js +2 -2
- package/dist/ui-feedback-plugin.umd.js +228 -250
- package/dist/ui-feedback-plugin.umd.js.map +1 -1
- package/package.json +13 -1
- package/dist/aws-CkWYvUhs.mjs +0 -134
- package/dist/index-B-1NC_TX.mjs +0 -12047
- package/dist/index-B-1NC_TX.mjs.map +0 -1
- package/dist/index-ClYqXfVs.mjs +0 -183
- package/dist/index-ClYqXfVs.mjs.map +0 -1
- package/dist/index-D6dCjvzy.mjs.map +0 -1
- package/dist/index.browser-CKeO7JYa.mjs +0 -190
- package/dist/index.browser-CKeO7JYa.mjs.map +0 -1
- package/dist/loadCognitoIdentity-V6H3EoBY.mjs +0 -957
- package/dist/loadCognitoIdentity-V6H3EoBY.mjs.map +0 -1
- package/dist/stream-collector-mh16BUWl.mjs +0 -4032
- package/dist/stream-collector-mh16BUWl.mjs.map +0 -1
- package/dist/tslib.es6-BNkLP3y-.mjs +0 -112
- package/dist/tslib.es6-BNkLP3y-.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -182,6 +182,12 @@ Everything — comments, replies, and screenshots — is stored directly in S3 a
|
|
|
182
182
|
|
|
183
183
|
The browser obtains **temporary, rotating credentials** from a Cognito Identity Pool. No long-lived AWS keys ever touch the browser. The IAM role follows the least-privilege principle (file-type restrictions, size limits, no delete permission).
|
|
184
184
|
|
|
185
|
+
The AWS SDK is not bundled with the plugin — install it alongside:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm install @aws-sdk/client-s3 @aws-sdk/credential-provider-cognito-identity
|
|
189
|
+
```
|
|
190
|
+
|
|
185
191
|
### Quick start
|
|
186
192
|
|
|
187
193
|
```js
|
|
@@ -412,6 +418,8 @@ No build step needed — load directly from a CDN:
|
|
|
412
418
|
|
|
413
419
|
Replace the `backend` block with any of the three provider configs shown above.
|
|
414
420
|
|
|
421
|
+
> The AWS backend is not usable this way — the AWS SDK is externalized from the bundle and needs a bundler (Vite, webpack, etc.) to resolve it. Firebase and Supabase remain fully usable via CDN as shown above.
|
|
422
|
+
|
|
415
423
|
---
|
|
416
424
|
|
|
417
425
|
## Local development
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
var h = Object.defineProperty;
|
|
2
|
+
var u = (c, t, s) => t in c ? h(c, t, { enumerable: !0, configurable: !0, writable: !0, value: s }) : c[t] = s;
|
|
3
|
+
var l = (c, t, s) => u(c, typeof t != "symbol" ? t + "" : t, s);
|
|
4
|
+
class g {
|
|
5
|
+
constructor(t) {
|
|
6
|
+
l(this, "cfg");
|
|
7
|
+
l(this, "_s3", null);
|
|
8
|
+
this.cfg = t;
|
|
9
|
+
}
|
|
10
|
+
async s3() {
|
|
11
|
+
if (this._s3) return this._s3;
|
|
12
|
+
const [{ S3Client: t }, { fromCognitoIdentityPool: s }] = await Promise.all([
|
|
13
|
+
import("@aws-sdk/client-s3"),
|
|
14
|
+
import("@aws-sdk/credential-provider-cognito-identity")
|
|
15
|
+
]);
|
|
16
|
+
return this._s3 = new t({
|
|
17
|
+
region: this.cfg.region,
|
|
18
|
+
credentials: s({
|
|
19
|
+
identityPoolId: this.cfg.identityPoolId,
|
|
20
|
+
clientConfig: { region: this.cfg.region }
|
|
21
|
+
})
|
|
22
|
+
}), this._s3;
|
|
23
|
+
}
|
|
24
|
+
async pageHash(t) {
|
|
25
|
+
const s = new TextEncoder().encode(t), e = await crypto.subtle.digest("SHA-256", s);
|
|
26
|
+
return Array.from(new Uint8Array(e)).map((n) => n.toString(16).padStart(2, "0")).join("").slice(0, 16);
|
|
27
|
+
}
|
|
28
|
+
async put(t, s, e) {
|
|
29
|
+
const { PutObjectCommand: n } = await import("@aws-sdk/client-s3");
|
|
30
|
+
await (await this.s3()).send(new n({
|
|
31
|
+
Bucket: this.cfg.bucket,
|
|
32
|
+
Key: t,
|
|
33
|
+
Body: s,
|
|
34
|
+
ContentType: e
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
async getJson(t) {
|
|
38
|
+
const { GetObjectCommand: s } = await import("@aws-sdk/client-s3"), i = await (await (await this.s3()).send(new s({ Bucket: this.cfg.bucket, Key: t }))).Body.transformToString();
|
|
39
|
+
return JSON.parse(i);
|
|
40
|
+
}
|
|
41
|
+
async listKeys(t) {
|
|
42
|
+
const { ListObjectsV2Command: s } = await import("@aws-sdk/client-s3"), e = await this.s3(), n = [];
|
|
43
|
+
let i;
|
|
44
|
+
do {
|
|
45
|
+
const o = await e.send(new s({
|
|
46
|
+
Bucket: this.cfg.bucket,
|
|
47
|
+
Prefix: t,
|
|
48
|
+
ContinuationToken: i
|
|
49
|
+
}));
|
|
50
|
+
for (const a of o.Contents ?? [])
|
|
51
|
+
a.Key && n.push(a.Key);
|
|
52
|
+
i = o.NextContinuationToken;
|
|
53
|
+
} while (i);
|
|
54
|
+
return n;
|
|
55
|
+
}
|
|
56
|
+
indexKey(t) {
|
|
57
|
+
return `ufp/_index/${t}.json`;
|
|
58
|
+
}
|
|
59
|
+
async lookupComment(t) {
|
|
60
|
+
const { p: s, h: e } = await this.getJson(this.indexKey(t));
|
|
61
|
+
return { key: `ufp/${s}/${e}/comments/${t}.json` };
|
|
62
|
+
}
|
|
63
|
+
async addComment(t) {
|
|
64
|
+
const s = await this.pageHash(t.pageUrl), e = crypto.randomUUID();
|
|
65
|
+
return await Promise.all([
|
|
66
|
+
this.put(
|
|
67
|
+
`ufp/${t.projectKey}/${s}/comments/${e}.json`,
|
|
68
|
+
JSON.stringify({ id: e, ...t }),
|
|
69
|
+
"application/json"
|
|
70
|
+
),
|
|
71
|
+
this.put(
|
|
72
|
+
this.indexKey(e),
|
|
73
|
+
JSON.stringify({ p: t.projectKey, h: s }),
|
|
74
|
+
"application/json"
|
|
75
|
+
)
|
|
76
|
+
]), e;
|
|
77
|
+
}
|
|
78
|
+
async getComments(t, s) {
|
|
79
|
+
const e = await this.pageHash(t), n = await this.listKeys(`ufp/${s}/${e}/comments/`);
|
|
80
|
+
return (await Promise.all(
|
|
81
|
+
n.map(
|
|
82
|
+
(o) => this.getJson(o).catch(() => null)
|
|
83
|
+
)
|
|
84
|
+
)).filter((o) => o !== null && !o.deleted).sort((o, a) => a.createdAt - o.createdAt);
|
|
85
|
+
}
|
|
86
|
+
async updateComment(t, s) {
|
|
87
|
+
const { key: e } = await this.lookupComment(t), n = await this.getJson(e);
|
|
88
|
+
await this.put(e, JSON.stringify({ ...n, ...s }), "application/json");
|
|
89
|
+
}
|
|
90
|
+
async deleteComment(t) {
|
|
91
|
+
const { key: s } = await this.lookupComment(t), e = await this.getJson(s);
|
|
92
|
+
await this.put(s, JSON.stringify({ ...e, deleted: !0 }), "application/json");
|
|
93
|
+
}
|
|
94
|
+
async addReply(t) {
|
|
95
|
+
const s = crypto.randomUUID();
|
|
96
|
+
return await this.put(
|
|
97
|
+
`ufp/replies/${t.commentId}/${s}.json`,
|
|
98
|
+
JSON.stringify({ id: s, ...t }),
|
|
99
|
+
"application/json"
|
|
100
|
+
), s;
|
|
101
|
+
}
|
|
102
|
+
async getReplies(t) {
|
|
103
|
+
const s = await this.listKeys(`ufp/replies/${t}/`);
|
|
104
|
+
return (await Promise.all(
|
|
105
|
+
s.map((n) => this.getJson(n).catch(() => null))
|
|
106
|
+
)).filter((n) => n !== null).sort((n, i) => n.createdAt - i.createdAt);
|
|
107
|
+
}
|
|
108
|
+
subscribeToComments(t, s, e) {
|
|
109
|
+
let n = !0;
|
|
110
|
+
const i = this.cfg.pollInterval ?? 5e3, o = async () => {
|
|
111
|
+
if (n) {
|
|
112
|
+
try {
|
|
113
|
+
const a = await this.getComments(t, s);
|
|
114
|
+
n && e(a);
|
|
115
|
+
} catch (a) {
|
|
116
|
+
console.error("[ui-feedback-plugin] AWS poll error:", a);
|
|
117
|
+
}
|
|
118
|
+
n && setTimeout(o, i);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return o(), () => {
|
|
122
|
+
n = !1;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async uploadScreenshot(t, s) {
|
|
126
|
+
const { p: e, h: n } = await this.getJson(this.indexKey(t)), i = `ufp/${e}/${n}/screenshots/${t}.png`, o = s.split(",")[1], a = atob(o), p = new Uint8Array(a.length);
|
|
127
|
+
for (let r = 0; r < a.length; r++) p[r] = a.charCodeAt(r);
|
|
128
|
+
return await this.put(i, p, "image/png"), `https://${this.cfg.bucket}.s3.${this.cfg.region}.amazonaws.com/${i}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
g as AWSAdapter
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=aws-DieAIoX4.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws-CkWYvUhs.mjs","sources":["../src/adapters/aws.ts"],"sourcesContent":["// AWS Cognito Identity Pool adapter — browser talks directly to S3, no backend required.\n// Requires @aws-sdk/client-s3 and @aws-sdk/credential-providers (both in devDependencies).\n//\n// ── AWS SETUP ────────────────────────────────────────────────────────────────────\n//\n// 1. CREATE S3 BUCKET\n// - Keep \"Block all public access\" ON (we'll add a narrow public policy for screenshots).\n// - Enable versioning (optional, helpful for recovery).\n//\n// 2. CONFIGURE CORS (required for browser access)\n// Go to Bucket → Permissions → CORS and paste:\n// [\n// {\n// \"AllowedOrigins\": [\"https://your-domain.com\"],\n// \"AllowedMethods\": [\"GET\", \"PUT\", \"HEAD\"],\n// \"AllowedHeaders\": [\"*\"],\n// \"ExposeHeaders\": [\"ETag\"],\n// \"MaxAgeSeconds\": 3000\n// }\n// ]\n// Use \"*\" for AllowedOrigins during local dev, restrict to real domains in production.\n//\n// 3. ADD BUCKET POLICY (public read for screenshots only)\n// Go to Bucket → Permissions → Bucket policy and paste (replace YOUR_BUCKET):\n// {\n// \"Version\": \"2012-10-17\",\n// \"Statement\": [{\n// \"Sid\": \"PublicReadScreenshots\",\n// \"Effect\": \"Allow\",\n// \"Principal\": \"*\",\n// \"Action\": \"s3:GetObject\",\n// \"Resource\": \"arn:aws:s3:::YOUR_BUCKET/ufp/*/screenshots/*\"\n// }]\n// }\n// Comments and replies remain private (only accessible via Cognito credentials).\n//\n// 4. CREATE IAM POLICY (least privilege — see README for full JSON)\n// Allows:\n// - s3:PutObject on ufp/*/comments/*.json (max 50 KB)\n// - s3:PutObject on ufp/replies/*/*.json (max 50 KB)\n// - s3:PutObject on ufp/_index/*.json (max 50 KB)\n// - s3:PutObject on ufp/*/screenshots/*.png (max 3 MB)\n// - s3:GetObject on ufp/*\n// - s3:ListBucket on the bucket with prefix ufp/*\n// NO s3:DeleteObject — soft delete is used instead.\n//\n// 5. CREATE IAM ROLE\n// - Trusted entity: Web Identity\n// - Identity provider: Amazon Cognito\n// - Attach the policy from step 4\n//\n// 6. CREATE COGNITO IDENTITY POOL\n// - Enable unauthenticated identities\n// - Unauthenticated role: the IAM role from step 5\n// - Authenticated role: none (or a different role)\n// - Copy the Identity Pool ID (format: us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n//\n// 7. CONFIGURE THE PLUGIN\n// initFeedbackPlugin({\n// backend: {\n// provider: 'aws',\n// region: 'us-east-1',\n// bucket: 'your-bucket-name',\n// identityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',\n// },\n// projectKey: 'my-project',\n// })\n//\n// ── DATA LAYOUT IN S3 ────────────────────────────────────────────────────────────\n//\n// ufp/{projectKey}/{pageHash}/comments/{commentId}.json\n// ufp/{projectKey}/{pageHash}/screenshots/{commentId}.png ← publicly readable\n// ufp/replies/{commentId}/{replyId}.json\n// ufp/_index/{commentId}.json ← { \"p\": projectKey, \"h\": pageHash }\n//\n// ── SOFT DELETE ──────────────────────────────────────────────────────────────────\n// deleteComment overwrites the comment JSON with { ...comment, deleted: true }.\n// The file stays in S3 but is filtered out on read. No s3:DeleteObject needed.\n//\n// ── REAL-TIME ────────────────────────────────────────────────────────────────────\n// S3 has no push. subscribeToComments polls on a configurable interval (default 5s).\n\nimport type { FeedbackComment, Reply, DatabaseAdapter, ScreenshotAdapter, AWSProviderConfig } from '../core/types'\nimport type { S3Client as S3ClientType } from '@aws-sdk/client-s3'\n\nexport class AWSAdapter implements DatabaseAdapter, ScreenshotAdapter {\n private cfg: AWSProviderConfig\n private _s3: S3ClientType | null = null\n\n constructor(config: AWSProviderConfig) {\n this.cfg = config\n }\n\n private async s3(): Promise<S3ClientType> {\n if (this._s3) return this._s3\n const [{ S3Client }, { fromCognitoIdentityPool }] = await Promise.all([\n import('@aws-sdk/client-s3'),\n import('@aws-sdk/credential-provider-cognito-identity'),\n ])\n this._s3 = new S3Client({\n region: this.cfg.region,\n credentials: fromCognitoIdentityPool({\n identityPoolId: this.cfg.identityPoolId,\n clientConfig: { region: this.cfg.region },\n }),\n })\n return this._s3\n }\n\n private async pageHash(url: string): Promise<string> {\n const data = new TextEncoder().encode(url)\n const buf = await crypto.subtle.digest('SHA-256', data)\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n .slice(0, 16)\n }\n\n private async put(key: string, body: string | Uint8Array, contentType: string): Promise<void> {\n const { PutObjectCommand } = await import('@aws-sdk/client-s3')\n const client = await this.s3()\n await client.send(new PutObjectCommand({\n Bucket: this.cfg.bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n }))\n }\n\n private async getJson<T>(key: string): Promise<T> {\n const { GetObjectCommand } = await import('@aws-sdk/client-s3')\n const client = await this.s3()\n const res = await client.send(new GetObjectCommand({ Bucket: this.cfg.bucket, Key: key }))\n const text = await res.Body!.transformToString()\n return JSON.parse(text) as T\n }\n\n private async listKeys(prefix: string): Promise<string[]> {\n const { ListObjectsV2Command } = await import('@aws-sdk/client-s3')\n const client = await this.s3()\n const keys: string[] = []\n let token: string | undefined\n do {\n const res = await client.send(new ListObjectsV2Command({\n Bucket: this.cfg.bucket,\n Prefix: prefix,\n ContinuationToken: token,\n }))\n for (const obj of res.Contents ?? []) {\n if (obj.Key) keys.push(obj.Key)\n }\n token = res.NextContinuationToken\n } while (token)\n return keys\n }\n\n private indexKey(id: string) {\n return `ufp/_index/${id}.json`\n }\n\n private async lookupComment(id: string): Promise<{ key: string }> {\n const { p, h } = await this.getJson<{ p: string; h: string }>(this.indexKey(id))\n return { key: `ufp/${p}/${h}/comments/${id}.json` }\n }\n\n async addComment(comment: Omit<FeedbackComment, 'id'>): Promise<string> {\n const hash = await this.pageHash(comment.pageUrl)\n const id = crypto.randomUUID()\n await Promise.all([\n this.put(\n `ufp/${comment.projectKey}/${hash}/comments/${id}.json`,\n JSON.stringify({ id, ...comment }),\n 'application/json'\n ),\n this.put(\n this.indexKey(id),\n JSON.stringify({ p: comment.projectKey, h: hash }),\n 'application/json'\n ),\n ])\n return id\n }\n\n async getComments(pageUrl: string, projectKey: string): Promise<FeedbackComment[]> {\n const hash = await this.pageHash(pageUrl)\n const keys = await this.listKeys(`ufp/${projectKey}/${hash}/comments/`)\n const rows = await Promise.all(\n keys.map(k =>\n this.getJson<FeedbackComment & { deleted?: boolean }>(k).catch(() => null)\n )\n )\n return rows\n .filter((r): r is FeedbackComment => r !== null && !r.deleted)\n .sort((a, b) => b.createdAt - a.createdAt)\n }\n\n async updateComment(id: string, data: Partial<FeedbackComment>): Promise<void> {\n const { key } = await this.lookupComment(id)\n const existing = await this.getJson<FeedbackComment>(key)\n await this.put(key, JSON.stringify({ ...existing, ...data }), 'application/json')\n }\n\n async deleteComment(id: string): Promise<void> {\n const { key } = await this.lookupComment(id)\n const existing = await this.getJson<FeedbackComment>(key)\n // Soft delete — overwrite with deleted flag. No s3:DeleteObject permission needed.\n await this.put(key, JSON.stringify({ ...existing, deleted: true }), 'application/json')\n }\n\n async addReply(reply: Omit<Reply, 'id'>): Promise<string> {\n const id = crypto.randomUUID()\n await this.put(\n `ufp/replies/${reply.commentId}/${id}.json`,\n JSON.stringify({ id, ...reply }),\n 'application/json'\n )\n return id\n }\n\n async getReplies(commentId: string): Promise<Reply[]> {\n const keys = await this.listKeys(`ufp/replies/${commentId}/`)\n const rows = await Promise.all(\n keys.map(k => this.getJson<Reply>(k).catch(() => null))\n )\n return rows\n .filter((r): r is Reply => r !== null)\n .sort((a, b) => a.createdAt - b.createdAt)\n }\n\n subscribeToComments(\n pageUrl: string,\n projectKey: string,\n callback: (comments: FeedbackComment[]) => void\n ): () => void {\n let active = true\n const interval = this.cfg.pollInterval ?? 5000\n\n const poll = async () => {\n if (!active) return\n try {\n const comments = await this.getComments(pageUrl, projectKey)\n if (active) callback(comments)\n } catch (err) {\n console.error('[ui-feedback-plugin] AWS poll error:', err)\n }\n if (active) setTimeout(poll, interval)\n }\n\n poll()\n return () => { active = false }\n }\n\n async uploadScreenshot(commentId: string, dataUrl: string): Promise<string> {\n const { p, h } = await this.getJson<{ p: string; h: string }>(this.indexKey(commentId))\n const key = `ufp/${p}/${h}/screenshots/${commentId}.png`\n\n const base64 = dataUrl.split(',')[1]\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n\n await this.put(key, bytes, 'image/png')\n return `https://${this.cfg.bucket}.s3.${this.cfg.region}.amazonaws.com/${key}`\n }\n}\n"],"names":["AWSAdapter","config","__publicField","S3Client","fromCognitoIdentityPool","n","url","data","buf","b","key","body","contentType","PutObjectCommand","GetObjectCommand","text","prefix","ListObjectsV2Command","client","keys","token","res","obj","id","p","h","comment","hash","pageUrl","projectKey","k","r","a","existing","reply","commentId","callback","active","interval","poll","comments","err","dataUrl","base64","binary","bytes","i"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aws-DieAIoX4.mjs","sources":["../src/adapters/aws.ts"],"sourcesContent":["// AWS Cognito Identity Pool adapter — browser talks directly to S3, no backend required.\n// Requires @aws-sdk/client-s3 and @aws-sdk/credential-providers (both in devDependencies).\n//\n// ── AWS SETUP ────────────────────────────────────────────────────────────────────\n//\n// 1. CREATE S3 BUCKET\n// - Keep \"Block all public access\" ON (we'll add a narrow public policy for screenshots).\n// - Enable versioning (optional, helpful for recovery).\n//\n// 2. CONFIGURE CORS (required for browser access)\n// Go to Bucket → Permissions → CORS and paste:\n// [\n// {\n// \"AllowedOrigins\": [\"https://your-domain.com\"],\n// \"AllowedMethods\": [\"GET\", \"PUT\", \"HEAD\"],\n// \"AllowedHeaders\": [\"*\"],\n// \"ExposeHeaders\": [\"ETag\"],\n// \"MaxAgeSeconds\": 3000\n// }\n// ]\n// Use \"*\" for AllowedOrigins during local dev, restrict to real domains in production.\n//\n// 3. ADD BUCKET POLICY (public read for screenshots only)\n// Go to Bucket → Permissions → Bucket policy and paste (replace YOUR_BUCKET):\n// {\n// \"Version\": \"2012-10-17\",\n// \"Statement\": [{\n// \"Sid\": \"PublicReadScreenshots\",\n// \"Effect\": \"Allow\",\n// \"Principal\": \"*\",\n// \"Action\": \"s3:GetObject\",\n// \"Resource\": \"arn:aws:s3:::YOUR_BUCKET/ufp/*/screenshots/*\"\n// }]\n// }\n// Comments and replies remain private (only accessible via Cognito credentials).\n//\n// 4. CREATE IAM POLICY (least privilege — see README for full JSON)\n// Allows:\n// - s3:PutObject on ufp/*/comments/*.json (max 50 KB)\n// - s3:PutObject on ufp/replies/*/*.json (max 50 KB)\n// - s3:PutObject on ufp/_index/*.json (max 50 KB)\n// - s3:PutObject on ufp/*/screenshots/*.png (max 3 MB)\n// - s3:GetObject on ufp/*\n// - s3:ListBucket on the bucket with prefix ufp/*\n// NO s3:DeleteObject — soft delete is used instead.\n//\n// 5. CREATE IAM ROLE\n// - Trusted entity: Web Identity\n// - Identity provider: Amazon Cognito\n// - Attach the policy from step 4\n//\n// 6. CREATE COGNITO IDENTITY POOL\n// - Enable unauthenticated identities\n// - Unauthenticated role: the IAM role from step 5\n// - Authenticated role: none (or a different role)\n// - Copy the Identity Pool ID (format: us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\n//\n// 7. CONFIGURE THE PLUGIN\n// initFeedbackPlugin({\n// backend: {\n// provider: 'aws',\n// region: 'us-east-1',\n// bucket: 'your-bucket-name',\n// identityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',\n// },\n// projectKey: 'my-project',\n// })\n//\n// ── DATA LAYOUT IN S3 ────────────────────────────────────────────────────────────\n//\n// ufp/{projectKey}/{pageHash}/comments/{commentId}.json\n// ufp/{projectKey}/{pageHash}/screenshots/{commentId}.png ← publicly readable\n// ufp/replies/{commentId}/{replyId}.json\n// ufp/_index/{commentId}.json ← { \"p\": projectKey, \"h\": pageHash }\n//\n// ── SOFT DELETE ──────────────────────────────────────────────────────────────────\n// deleteComment overwrites the comment JSON with { ...comment, deleted: true }.\n// The file stays in S3 but is filtered out on read. No s3:DeleteObject needed.\n//\n// ── REAL-TIME ────────────────────────────────────────────────────────────────────\n// S3 has no push. subscribeToComments polls on a configurable interval (default 5s).\n\nimport type { FeedbackComment, Reply, DatabaseAdapter, ScreenshotAdapter, AWSProviderConfig } from '../core/types'\nimport type { S3Client as S3ClientType } from '@aws-sdk/client-s3'\n\nexport class AWSAdapter implements DatabaseAdapter, ScreenshotAdapter {\n private cfg: AWSProviderConfig\n private _s3: S3ClientType | null = null\n\n constructor(config: AWSProviderConfig) {\n this.cfg = config\n }\n\n private async s3(): Promise<S3ClientType> {\n if (this._s3) return this._s3\n const [{ S3Client }, { fromCognitoIdentityPool }] = await Promise.all([\n import('@aws-sdk/client-s3'),\n import('@aws-sdk/credential-provider-cognito-identity'),\n ])\n this._s3 = new S3Client({\n region: this.cfg.region,\n credentials: fromCognitoIdentityPool({\n identityPoolId: this.cfg.identityPoolId,\n clientConfig: { region: this.cfg.region },\n }),\n })\n return this._s3\n }\n\n private async pageHash(url: string): Promise<string> {\n const data = new TextEncoder().encode(url)\n const buf = await crypto.subtle.digest('SHA-256', data)\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n .slice(0, 16)\n }\n\n private async put(key: string, body: string | Uint8Array, contentType: string): Promise<void> {\n const { PutObjectCommand } = await import('@aws-sdk/client-s3')\n const client = await this.s3()\n await client.send(new PutObjectCommand({\n Bucket: this.cfg.bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n }))\n }\n\n private async getJson<T>(key: string): Promise<T> {\n const { GetObjectCommand } = await import('@aws-sdk/client-s3')\n const client = await this.s3()\n const res = await client.send(new GetObjectCommand({ Bucket: this.cfg.bucket, Key: key }))\n const text = await res.Body!.transformToString()\n return JSON.parse(text) as T\n }\n\n private async listKeys(prefix: string): Promise<string[]> {\n const { ListObjectsV2Command } = await import('@aws-sdk/client-s3')\n const client = await this.s3()\n const keys: string[] = []\n let token: string | undefined\n do {\n const res = await client.send(new ListObjectsV2Command({\n Bucket: this.cfg.bucket,\n Prefix: prefix,\n ContinuationToken: token,\n }))\n for (const obj of res.Contents ?? []) {\n if (obj.Key) keys.push(obj.Key)\n }\n token = res.NextContinuationToken\n } while (token)\n return keys\n }\n\n private indexKey(id: string) {\n return `ufp/_index/${id}.json`\n }\n\n private async lookupComment(id: string): Promise<{ key: string }> {\n const { p, h } = await this.getJson<{ p: string; h: string }>(this.indexKey(id))\n return { key: `ufp/${p}/${h}/comments/${id}.json` }\n }\n\n async addComment(comment: Omit<FeedbackComment, 'id'>): Promise<string> {\n const hash = await this.pageHash(comment.pageUrl)\n const id = crypto.randomUUID()\n await Promise.all([\n this.put(\n `ufp/${comment.projectKey}/${hash}/comments/${id}.json`,\n JSON.stringify({ id, ...comment }),\n 'application/json'\n ),\n this.put(\n this.indexKey(id),\n JSON.stringify({ p: comment.projectKey, h: hash }),\n 'application/json'\n ),\n ])\n return id\n }\n\n async getComments(pageUrl: string, projectKey: string): Promise<FeedbackComment[]> {\n const hash = await this.pageHash(pageUrl)\n const keys = await this.listKeys(`ufp/${projectKey}/${hash}/comments/`)\n const rows = await Promise.all(\n keys.map(k =>\n this.getJson<FeedbackComment & { deleted?: boolean }>(k).catch(() => null)\n )\n )\n return rows\n .filter((r): r is FeedbackComment => r !== null && !r.deleted)\n .sort((a, b) => b.createdAt - a.createdAt)\n }\n\n async updateComment(id: string, data: Partial<FeedbackComment>): Promise<void> {\n const { key } = await this.lookupComment(id)\n const existing = await this.getJson<FeedbackComment>(key)\n await this.put(key, JSON.stringify({ ...existing, ...data }), 'application/json')\n }\n\n async deleteComment(id: string): Promise<void> {\n const { key } = await this.lookupComment(id)\n const existing = await this.getJson<FeedbackComment>(key)\n // Soft delete — overwrite with deleted flag. No s3:DeleteObject permission needed.\n await this.put(key, JSON.stringify({ ...existing, deleted: true }), 'application/json')\n }\n\n async addReply(reply: Omit<Reply, 'id'>): Promise<string> {\n const id = crypto.randomUUID()\n await this.put(\n `ufp/replies/${reply.commentId}/${id}.json`,\n JSON.stringify({ id, ...reply }),\n 'application/json'\n )\n return id\n }\n\n async getReplies(commentId: string): Promise<Reply[]> {\n const keys = await this.listKeys(`ufp/replies/${commentId}/`)\n const rows = await Promise.all(\n keys.map(k => this.getJson<Reply>(k).catch(() => null))\n )\n return rows\n .filter((r): r is Reply => r !== null)\n .sort((a, b) => a.createdAt - b.createdAt)\n }\n\n subscribeToComments(\n pageUrl: string,\n projectKey: string,\n callback: (comments: FeedbackComment[]) => void\n ): () => void {\n let active = true\n const interval = this.cfg.pollInterval ?? 5000\n\n const poll = async () => {\n if (!active) return\n try {\n const comments = await this.getComments(pageUrl, projectKey)\n if (active) callback(comments)\n } catch (err) {\n console.error('[ui-feedback-plugin] AWS poll error:', err)\n }\n if (active) setTimeout(poll, interval)\n }\n\n poll()\n return () => { active = false }\n }\n\n async uploadScreenshot(commentId: string, dataUrl: string): Promise<string> {\n const { p, h } = await this.getJson<{ p: string; h: string }>(this.indexKey(commentId))\n const key = `ufp/${p}/${h}/screenshots/${commentId}.png`\n\n const base64 = dataUrl.split(',')[1]\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n\n await this.put(key, bytes, 'image/png')\n return `https://${this.cfg.bucket}.s3.${this.cfg.region}.amazonaws.com/${key}`\n }\n}\n"],"names":["AWSAdapter","config","__publicField","S3Client","fromCognitoIdentityPool","url","data","buf","b","key","body","contentType","PutObjectCommand","GetObjectCommand","text","prefix","ListObjectsV2Command","client","keys","token","res","obj","id","p","h","comment","hash","pageUrl","projectKey","k","r","a","existing","reply","commentId","callback","active","interval","poll","comments","err","dataUrl","base64","binary","bytes","i"],"mappings":"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"}
|