annotate-kit 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lavish-mitra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ <div align="center">
2
+
3
+ # ๐Ÿ–๏ธ annotate-kit
4
+
5
+ **A drop-in, in-app annotation & visual-feedback overlay for React.**
6
+
7
+ Pin and draw directly on your live site โ†’ capture the element, screenshot, console & network โ†’
8
+ export one clean prompt for your developer or any AI assistant.
9
+
10
+ Backend-agnostic. Safe by default. Free, and works with **zero sign-up**.
11
+
12
+ [![npm](https://img.shields.io/npm/v/annotate-kit.svg)](https://www.npmjs.com/package/annotate-kit)
13
+ [![CI](https://github.com/lavish-mitra/annotate-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/lavish-mitra/annotate-kit/actions/workflows/ci.yml)
14
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
15
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
16
+ [![React 18 ยท 19](https://img.shields.io/badge/React-18%20%C2%B7%2019-61dafb.svg)](https://react.dev)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## Why annotate-kit?
23
+
24
+ Getting precise visual feedback usually means blurry screenshots and vague messages like
25
+ _"the button looks off."_ annotate-kit lets anyone mark **exactly** what they mean, right on the live
26
+ page, and hands your developer (or any AI coding assistant) a structured, pinpointed change list โ€”
27
+ with the element selector, a screenshot, and the console/network context already attached.
28
+
29
+ - ๐Ÿ“Œ **Pin & draw** โ€” box, arrow, circle, freehand โ€” with colours, type, priority, status & replies
30
+ - ๐Ÿ–ผ๏ธ **Screenshots** โ€” per-element, full-page, or viewport, with a built-in **markup editor** (draw arrows/boxes/pen); password fields blurred automatically
31
+ - ๐Ÿ› **Reproducible context** โ€” browser/OS/viewport + opt-in console and network capture
32
+ - ๐Ÿค– **Tool-agnostic export** โ€” clean markdown *or* structured JSON for any dev or AI assistant
33
+ - ๐Ÿ”Œ **Any backend** โ€” zero-backend, your REST API, Supabase, Firebase, or write your own (9 methods)
34
+ - โšก **Realtime** โ€” live updates on Supabase & Firebase (falls back to polling elsewhere)
35
+ - ๐Ÿ“ค **Deliver anywhere** โ€” optional one-click GitHub issue / webhook / Slack from the Send panel
36
+ - ๐Ÿ”’ **Safe by default** โ€” ships admin-only, marks private, capture opt-in, secrets redacted
37
+ - ๐ŸŽจ **Themeable** โ€” light / dark / auto + your brand accent colour, self-contained styles
38
+ - โ™ฟ **Accessible** โ€” dialog semantics, labelled controls, full keyboard support
39
+
40
+ ## How it works
41
+
42
+ ```mermaid
43
+ flowchart LR
44
+ U([Your users]) -->|pin and draw| A["Annotate overlay"]
45
+ A -->|9-method contract| AD[StorageAdapter]
46
+ AD --> LB["local / idb (zero backend)"]
47
+ AD --> RB["your REST API"]
48
+ AD --> SB[Supabase]
49
+ AD --> FB[Firebase]
50
+ A -->|export| P["buildDevPrompt / buildDevContext"]
51
+ P --> DEV([Developer or AI assistant])
52
+ ```
53
+
54
+ ---
55
+
56
+ ## ๐Ÿš€ 30-second start (zero backend, no sign-up)
57
+
58
+ ```bash
59
+ npm install annotate-kit
60
+ # or straight from GitHub: npm install github:lavish-mitra/annotate-kit
61
+ ```
62
+
63
+ ```tsx
64
+ import { Annotate } from 'annotate-kit'
65
+ import { createLocalAdapter } from 'annotate-kit/local'
66
+ import 'annotate-kit/styles.css'
67
+
68
+ export function AnnotateMount() {
69
+ return <Annotate adapter={createLocalAdapter()} />
70
+ }
71
+ ```
72
+
73
+ Render `<AnnotateMount />` once near your app root. That's it โ€” a floating tool appears, and marks are
74
+ saved in the browser. Ready for a demo or local dev without any server.
75
+
76
+ > Ships prebuilt (`dist/` is committed), so there's no build step on install. Peer deps: `react` and
77
+ > `react-dom` (already in your app).
78
+
79
+ ---
80
+
81
+ ## Pick a backend
82
+
83
+ The overlay talks to storage through **one small adapter** โ€” choose what fits. **None require a
84
+ third-party sign-up.**
85
+
86
+ | Adapter | Import | Best for | Multi-user? |
87
+ |---|---|---|---|
88
+ | **local** | `annotate-kit/local` | Demos, local dev, single user | โŒ single browser |
89
+ | **idb** | `annotate-kit/idb` | Same, but many/large screenshots (IndexedDB) | โŒ single browser |
90
+ | **rest** | `annotate-kit/rest` | Your own API on any platform | โœ… your server enforces |
91
+ | **supabase** | `annotate-kit/supabase` | Supabase projects | โœ… RLS enforces |
92
+ | **firebase** | `annotate-kit/firebase` | Firebase projects | โœ… rules enforce |
93
+
94
+ **Your own API (any platform):**
95
+
96
+ ```tsx
97
+ import { createRestAdapter } from 'annotate-kit/rest'
98
+
99
+ const adapter = createRestAdapter({
100
+ baseUrl: '/api/annotations',
101
+ headers: () => ({ Authorization: `Bearer ${getToken()}` }), // optional
102
+ })
103
+ ```
104
+
105
+ See [`src/adapters/rest.ts`](src/adapters/rest.ts) for the exact 9-endpoint contract your API implements.
106
+
107
+ <details>
108
+ <summary><b>Quick start โ€” Supabase</b></summary>
109
+
110
+ **1. Apply the backend** (once): run [`backend/supabase/migration.sql`](backend/supabase/migration.sql)
111
+ via `supabase db push` or the SQL editor. It creates the table, a global toggle, a private
112
+ `annotations` bucket, an author-stamping trigger, and RLS policies safe for the anon key.
113
+
114
+ **2. Mark your admins**: in Supabase Auth, give admin users
115
+ `app_metadata.role = "admin"` (or `app_metadata.annotate_admin = true`).
116
+
117
+ **3. Mount it:**
118
+
119
+ ```tsx
120
+ import { Annotate } from 'annotate-kit'
121
+ import { createSupabaseAdapter } from 'annotate-kit/supabase'
122
+ import { useMemo } from 'react'
123
+ import { useLocation, useNavigate } from 'react-router-dom'
124
+ import { supabase } from './lib/supabase'
125
+ import 'annotate-kit/styles.css'
126
+
127
+ export function AnnotateMount() {
128
+ const adapter = useMemo(() => createSupabaseAdapter({ client: supabase }), [])
129
+ const { pathname } = useLocation()
130
+ const navigate = useNavigate()
131
+ return <Annotate adapter={adapter} route={pathname} onNavigate={navigate} />
132
+ }
133
+ ```
134
+
135
+ The tool ships **admin-only** โ€” an admin enables it for everyone from the panel when ready.
136
+
137
+ </details>
138
+
139
+ <details>
140
+ <summary><b>Quick start โ€” Firebase</b></summary>
141
+
142
+ **1. Apply the rules + indexes** (once): merge [`backend/firebase/firestore.rules`](backend/firebase/firestore.rules)
143
+ and [`backend/firebase/storage.rules`](backend/firebase/storage.rules) into your rules, add the composite
144
+ indexes from [`backend/firebase/firestore.indexes.json`](backend/firebase/firestore.indexes.json), and deploy:
145
+
146
+ ```bash
147
+ firebase deploy --only firestore:rules,firestore:indexes,storage
148
+ ```
149
+
150
+ **2. Mark your admins** via a custom claim (Admin SDK):
151
+
152
+ ```js
153
+ await admin.auth().setCustomUserClaims(uid, { admin: true })
154
+ ```
155
+
156
+ **3. Mount it:**
157
+
158
+ ```tsx
159
+ import { Annotate } from 'annotate-kit'
160
+ import { createFirebaseAdapter } from 'annotate-kit/firebase'
161
+ import { useMemo } from 'react'
162
+ import { db, storage, auth } from './lib/firebase'
163
+ import 'annotate-kit/styles.css'
164
+
165
+ export function AnnotateMount() {
166
+ const adapter = useMemo(() => createFirebaseAdapter({ firestore: db, storage, auth }), [])
167
+ return <Annotate adapter={adapter} route={window.location.pathname} />
168
+ }
169
+ ```
170
+
171
+ </details>
172
+
173
+ ---
174
+
175
+ ## `<Annotate>` props
176
+
177
+ | Prop | Type | Default | Notes |
178
+ |---|---|---|---|
179
+ | `adapter` | `StorageAdapter` | โ€” | **Required.** From `annotate-kit/{local,idb,rest,supabase,firebase}`, or your own. |
180
+ | `route` | `string` | `location.pathname` | Pass your router's pathname so marks are page-aware in an SPA. |
181
+ | `onNavigate` | `(path) => void` | full-page load | Your router's `navigate`, for smooth cross-page "find on page". |
182
+ | `user` | `{ email; name; role }` | from adapter | Override author attribution. |
183
+ | `onToast` | `(kind, msg) => void` | built-in toast | Hook into your app's notifications. |
184
+ | `pollMs` | `number` | `15000` | Refresh cadence while the tool/panel is open. |
185
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'light'` | Colour theme for the widget's own panels (not the host page). |
186
+ | `accent` | `string` | brand indigo | Brand accent colour (any valid CSS colour, e.g. `#0ea5e9`). Invalid values ignored. |
187
+ | `captureConsole` | `boolean` | `false` | Attach a rolling console/JS-error buffer to each mark (may contain secrets). |
188
+ | `captureNetwork` | `boolean` | `false` | Attach failed/slow fetch+XHR calls; URLs trimmed + redacted. |
189
+ | `captureDomSnapshot` | `boolean` | `false` | Attach a sanitised HTML snapshot of the marked element (scripts/handlers stripped, passwords blanked). |
190
+ | `captureText` | `boolean` | `true` | Capture on-screen element text. Turn off for sensitive apps. |
191
+ | `captureFullUrl` | `boolean` | `false` | Store the full URL (query + hash) instead of just `origin + pathname`. |
192
+ | `redactScreenshots` | `boolean` | `true` | Blur password fields and `[data-annot-redact]` elements before capture. |
193
+ | `redact` | `(text) => string` | built-in | Redact secrets/PII from captured text + console. Default masks JWTs, tokens, emails, long digit runs. |
194
+ | `singleKeyShortcuts` | `boolean` | `true` | Bare-letter tool hotkeys (P/B/A/C/D). Turn off to avoid host-app key collisions. |
195
+ | `deliveries` | `Delivery[]` | `[]` | Extra "send toโ€ฆ" buttons in the Send panel โ€” GitHub issue / webhook / Slack / your own. |
196
+
197
+ > If your adapter supports realtime (Supabase & Firebase do), the overlay updates **live** โ€” you don't
198
+ > need to tune `pollMs`.
199
+
200
+ The tool only renders for users who may use it, and it ships **admin-only** with marks defaulting to
201
+ **private** โ€” so it's safe to mount unconditionally: end users see nothing until an admin enables it.
202
+
203
+ **Keyboard:** `Shift+A` pin ยท `P/B/A/C/D` tools ยท `Esc` cancel/close ยท `โŒ˜/Ctrl+Enter` save ยท
204
+ `Ctrl+Z` undo ยท `Shift+H` hide/show ยท `?` help.
205
+
206
+ ---
207
+
208
+ ## Sending feedback
209
+
210
+ Open the dock โ†’ mark things โ†’ **Send**. You get a structured markdown prompt (grouped by page, ordered
211
+ by priority, each item naming the exact element, request, environment, and console). Copy it into your
212
+ dev chat or any AI assistant. As items get built, flip them to **Done** in the panel.
213
+
214
+ Two exports are available for your own tooling:
215
+
216
+ ```ts
217
+ import { buildDevPrompt, buildDevContext } from 'annotate-kit'
218
+
219
+ const markdown = buildDevPrompt(items, { includeConsole: true, includeNetwork: true })
220
+ const json = buildDevContext(items) // { total, items: [{ element, note, console, network, โ€ฆ }] }
221
+ ```
222
+
223
+ Both are pure functions. The export treats all annotation text as **untrusted data** (a stored
224
+ prompt-injection defence) and omits console/network unless you opt in.
225
+
226
+ ### Send straight to your tools (optional)
227
+
228
+ Add one-click targets to the Send panel. `githubIssueDelivery` needs **no token**; webhook/Slack use a
229
+ URL you provide:
230
+
231
+ ```tsx
232
+ import { githubIssueDelivery, createWebhookDelivery, createSlackDelivery } from 'annotate-kit'
233
+
234
+ <Annotate adapter={adapter} deliveries={[
235
+ githubIssueDelivery({ repo: 'you/your-repo' }), // opens a prefilled issue (no token)
236
+ createWebhookDelivery({ url: 'https://api.you.dev/feedback' }), // POSTs { prompt, context }
237
+ createSlackDelivery({ webhookUrl: process.env.SLACK_HOOK! }), // posts to Slack
238
+ ]} />
239
+ ```
240
+
241
+ ---
242
+
243
+ > **Client-side note:** webhook/Slack targets run in the browser โ€” their URL/headers are visible to end
244
+ > users, so use non-secret endpoints or proxy secrets through your own server (`githubIssueDelivery`
245
+ > needs no token). Also memoize your `adapter` (`useMemo`) so realtime/polling don't re-subscribe each render.
246
+
247
+ ## ๐Ÿ”’ Security & privacy
248
+
249
+ annotate-kit captures live-app context, so the defaults are conservative โ€” please review before
250
+ shipping. See [SECURITY.md](SECURITY.md) for the full policy and how to report issues.
251
+
252
+ - **Admin-only until enabled**, new marks **private** by default.
253
+ - **Screenshots are access-controlled** (Supabase/Firebase): private-mark shots are readable only by
254
+ their owner or an admin; uploads are capped to 5 MB images and can't overwrite existing files.
255
+ - **Console & network capture are opt-in** and only hook while the tool is active โ€” no global patching
256
+ at import, and host wrappers (Sentry etc.) are never clobbered.
257
+ - **Redaction**: captured text/console is scrubbed (JWTs, tokens, emails, long digit runs); screenshots
258
+ blur password fields and any `data-annot-redact` element. URLs are stored as `origin + pathname`
259
+ unless you opt into `captureFullUrl`.
260
+ - **Server-verified identity**: Supabase stamps `created_by`/role from the JWT and pins it immutable;
261
+ Firestore pins `created_by` on update. Authorization is enforced by your backend, not the client.
262
+ - **Local / idb adapters** are single-browser only โ€” no multi-user boundary. Use a server-backed
263
+ adapter for shared data.
264
+
265
+ > โš ๏ธ The `local` and `idb` adapters store everything in one browser with no access control โ€” great for
266
+ > demos and single-user, never for shared/multi-tenant data.
267
+
268
+ ---
269
+
270
+ ## Write your own adapter
271
+
272
+ Implement 9 methods against any store and the whole UI just works:
273
+
274
+ ```ts
275
+ import type { StorageAdapter } from 'annotate-kit'
276
+
277
+ const myAdapter: StorageAdapter = {
278
+ getAccess, // who is looking + what they may do
279
+ setAccess, // flip the global toggle (admin)
280
+ list, // all visible marks, with screenshot_url resolved
281
+ save, // persist a new mark โ†’ { id }
282
+ update, // patch status/note/type/priority by id
283
+ remove, // delete a mark (+ its screenshot)
284
+ addReply, // append a reply
285
+ clearResolved, // delete done/dismissed
286
+ uploadScreenshot, // (File, { visibility }) โ†’ { path }
287
+ }
288
+ ```
289
+
290
+ See [`src/adapter.ts`](src/adapter.ts) for the exact contract.
291
+
292
+ ---
293
+
294
+ ## Contributing
295
+
296
+ Contributions are welcome โ€” bug fixes, adapters, docs, and ideas. Please read
297
+ [CONTRIBUTING.md](CONTRIBUTING.md), and note the [Code of Conduct](CODE_OF_CONDUCT.md).
298
+
299
+ ```bash
300
+ git clone https://github.com/lavish-mitra/annotate-kit.git
301
+ cd annotate-kit && npm install
302
+ npm run typecheck && npm test && npm run build
303
+ ```
304
+
305
+ ## License
306
+
307
+ [MIT](LICENSE) ยฉ annotate-kit contributors โ€” free to use, modify, and distribute.
@@ -0,0 +1,22 @@
1
+ {
2
+ "//": "annotate-kit โ€” composite indexes so non-admin list() can order newest-first while filtering. Deploy with: firebase deploy --only firestore:indexes (rename 'annotations' if you set collectionPath).",
3
+ "indexes": [
4
+ {
5
+ "collectionGroup": "annotations",
6
+ "queryScope": "COLLECTION",
7
+ "fields": [
8
+ { "fieldPath": "visibility", "order": "ASCENDING" },
9
+ { "fieldPath": "id", "order": "DESCENDING" }
10
+ ]
11
+ },
12
+ {
13
+ "collectionGroup": "annotations",
14
+ "queryScope": "COLLECTION",
15
+ "fields": [
16
+ { "fieldPath": "created_by", "order": "ASCENDING" },
17
+ { "fieldPath": "id", "order": "DESCENDING" }
18
+ ]
19
+ }
20
+ ],
21
+ "fieldOverrides": []
22
+ }
@@ -0,0 +1,84 @@
1
+ rules_version = '2';
2
+
3
+ // annotate-kit โ€” Firestore rules. Merge these into your firestore.rules (inside the
4
+ // existing `match /databases/{database}/documents { ... }` block) and deploy:
5
+ // firebase deploy --only firestore:rules
6
+ //
7
+ // Admin model: a user is an admin when their ID-token has custom claim
8
+ // admin == true OR role == 'admin'
9
+ // (set via the Admin SDK: admin.auth().setCustomUserClaims(uid, { admin: true })).
10
+ // The client adapter's isAdmin default matches this.
11
+ //
12
+ // Security notes (kept at parity with the Supabase backend):
13
+ // * Ships admin-only: non-admin read/create is gated on the global toggle at
14
+ // annotate_settings/global (enabled == true). New marks default to visibility:'private'.
15
+ // * created_by is pinned to your own email on create and IMMUTABLE on update โ€” no author forgery.
16
+ // * Anyone who can SEE a mark (public, or owner, or admin) may append a reply.
17
+ // * Payload sizes are capped to blunt storage/cost abuse.
18
+ // * NOTE (created_by_name/role): these are client-written display values on Firebase and are not
19
+ // server-verified (unlike Supabase's trigger). Treat only created_by (email) as trustworthy.
20
+ // * NOTE (screenshots): getDownloadURL tokens are bearer links; a private screenshot's URL is only
21
+ // stored in owner/admin-readable docs, but the link itself bypasses Storage rules once known.
22
+ //
23
+ // NOTE: if you set collectionPath / settingsPath on the adapter, rename the match/settings paths below.
24
+
25
+ service cloud.firestore {
26
+ match /databases/{database}/documents {
27
+
28
+ function annotSignedIn() { return request.auth != null; }
29
+ function annotIsAdmin() {
30
+ return annotSignedIn() && (request.auth.token.admin == true || request.auth.token.role == 'admin');
31
+ }
32
+ function annotMyEmail() { return request.auth.token.email; }
33
+ // Global on/off toggle โ€” mirrors Supabase's annotate_enabled(). Reads the settings doc.
34
+ function annotEnabled() {
35
+ return exists(/databases/$(database)/documents/annotate_settings/global)
36
+ && get(/databases/$(database)/documents/annotate_settings/global).data.enabled == true;
37
+ }
38
+ function annotSizeOk() {
39
+ let d = request.resource.data;
40
+ return (!('note' in d) || d.note.size() <= 5000)
41
+ && (!('target_text' in d) || d.target_text.size() <= 2000)
42
+ && (!('target_selector' in d) || d.target_selector.size() <= 1000)
43
+ && (!('points' in d) || d.points == null || d.points.size() <= 4000)
44
+ && (!('console_logs' in d) || d.console_logs == null || d.console_logs.size() <= 50)
45
+ && (!('network_logs' in d) || d.network_logs == null || d.network_logs.size() <= 100)
46
+ && (!('replies' in d) || d.replies == null || d.replies.size() <= 200)
47
+ && (!('dom_snapshot' in d) || d.dom_snapshot == null || d.dom_snapshot.size() <= 20000);
48
+ }
49
+
50
+ match /annotations/{id} {
51
+ // Admins see everything; everyone else sees their own + public marks โ€” but only while enabled.
52
+ allow read: if annotIsAdmin()
53
+ || (annotEnabled() && annotSignedIn()
54
+ && (resource.data.created_by == annotMyEmail() || resource.data.visibility == 'public'));
55
+ // Create only as yourself, only while enabled (or admin), with valid visibility + bounded size.
56
+ allow create: if (annotIsAdmin() || annotEnabled())
57
+ && annotSignedIn()
58
+ && request.resource.data.created_by == annotMyEmail()
59
+ && request.resource.data.visibility in ['public', 'private']
60
+ && annotSizeOk();
61
+ // Edit your own (or admin) with created_by immutable, OR append a reply to any PUBLIC mark you can see.
62
+ allow update: if (
63
+ (annotIsAdmin() || (annotSignedIn() && resource.data.created_by == annotMyEmail()))
64
+ && request.resource.data.created_by == resource.data.created_by
65
+ && request.resource.data.visibility in ['public', 'private']
66
+ && annotSizeOk()
67
+ ) || (
68
+ annotSignedIn()
69
+ && resource.data.visibility == 'public'
70
+ && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['replies', 'updated_at'])
71
+ && request.resource.data.created_by == resource.data.created_by
72
+ && annotSizeOk()
73
+ );
74
+ // Delete your own, or admin.
75
+ allow delete: if annotIsAdmin()
76
+ || (annotSignedIn() && resource.data.created_by == annotMyEmail());
77
+ }
78
+
79
+ match /annotate_settings/{doc} {
80
+ allow read: if annotSignedIn(); // everyone can read the on/off toggle
81
+ allow write: if annotIsAdmin(); // only admins flip it
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,37 @@
1
+ rules_version = '2';
2
+
3
+ // annotate-kit โ€” Cloud Storage rules for annotation screenshots. Merge into your storage.rules and deploy:
4
+ // firebase deploy --only storage
5
+ //
6
+ // Screenshots are stored under annotations/<visibility>/<uid>/<file> (the adapter builds this path):
7
+ // * public/<uid>/โ€ฆ readable by any signed-in user (these back 'public' marks)
8
+ // * private/<uid>/โ€ฆ readable ONLY by the owner (uid) or an admin
9
+ // Uploads may only go into the caller's OWN uid folder, are limited to <5MB images, and existing
10
+ // objects cannot be overwritten. Delete is restricted to the owner or an admin.
11
+ //
12
+ // NOTE: if you set storagePath on the adapter, rename the 'annotations' segment below to match.
13
+
14
+ service firebase.storage {
15
+ match /b/{bucket}/o {
16
+
17
+ function annotSignedIn() { return request.auth != null; }
18
+ function annotIsAdmin() {
19
+ return annotSignedIn() && (request.auth.token.admin == true || request.auth.token.role == 'admin');
20
+ }
21
+ function annotImageOk() {
22
+ return request.resource.size < 5 * 1024 * 1024
23
+ && request.resource.contentType.matches('image/.*');
24
+ }
25
+
26
+ match /annotations/{visibility}/{uid}/{allPaths=**} {
27
+ allow read: if annotSignedIn()
28
+ && (visibility == 'public' || request.auth.uid == uid || annotIsAdmin());
29
+ allow create: if annotSignedIn()
30
+ && request.auth.uid == uid
31
+ && (visibility == 'public' || visibility == 'private')
32
+ && annotImageOk();
33
+ allow delete: if annotSignedIn() && (request.auth.uid == uid || annotIsAdmin());
34
+ allow update: if false; // no overwriting an existing object
35
+ }
36
+ }
37
+ }