@vibesdotdev/media 0.0.0-stage → 1.0.0-rc.4

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.
Files changed (278) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +95 -2
  3. package/dist/analyzers/frame-guard.d.ts +6 -0
  4. package/dist/analyzers/frame-guard.d.ts.map +1 -0
  5. package/dist/analyzers/frame-guard.js +10 -0
  6. package/dist/analyzers/frame-guard.js.map +1 -0
  7. package/dist/analyzers/motion-math.d.ts +66 -0
  8. package/dist/analyzers/motion-math.d.ts.map +1 -0
  9. package/dist/analyzers/motion-math.js +356 -0
  10. package/dist/analyzers/motion-math.js.map +1 -0
  11. package/dist/analyzers/reference-motion-analyzer.impl.d.ts +23 -0
  12. package/dist/analyzers/reference-motion-analyzer.impl.d.ts.map +1 -0
  13. package/dist/analyzers/reference-motion-analyzer.impl.js +69 -0
  14. package/dist/analyzers/reference-motion-analyzer.impl.js.map +1 -0
  15. package/dist/analyzers/reference-motion.descriptor.d.ts +11 -0
  16. package/dist/analyzers/reference-motion.descriptor.d.ts.map +1 -0
  17. package/dist/analyzers/reference-motion.descriptor.js +21 -0
  18. package/dist/analyzers/reference-motion.descriptor.js.map +1 -0
  19. package/dist/capture-client.d.ts +34 -0
  20. package/dist/capture-client.d.ts.map +1 -0
  21. package/dist/capture-client.js +163 -0
  22. package/dist/capture-client.js.map +1 -0
  23. package/dist/config/media.uploads.config.descriptor.d.ts +27 -0
  24. package/dist/config/media.uploads.config.descriptor.d.ts.map +1 -0
  25. package/dist/config/media.uploads.config.descriptor.js +46 -0
  26. package/dist/config/media.uploads.config.descriptor.js.map +1 -0
  27. package/dist/config/uploads.d.ts +11 -0
  28. package/dist/config/uploads.d.ts.map +1 -0
  29. package/dist/config/uploads.js +25 -0
  30. package/dist/config/uploads.js.map +1 -0
  31. package/dist/docs/artifact-references.docs.descriptor.d.ts +4 -0
  32. package/dist/docs/artifact-references.docs.descriptor.d.ts.map +1 -0
  33. package/dist/docs/artifact-references.docs.descriptor.js +69 -0
  34. package/dist/docs/artifact-references.docs.descriptor.js.map +1 -0
  35. package/dist/docs/image-transform.docs.descriptor.d.ts +4 -0
  36. package/dist/docs/image-transform.docs.descriptor.d.ts.map +1 -0
  37. package/dist/docs/image-transform.docs.descriptor.js +52 -0
  38. package/dist/docs/image-transform.docs.descriptor.js.map +1 -0
  39. package/dist/index.d.ts +48 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +49 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/interfaces/capture-provider.d.ts +33 -0
  44. package/dist/interfaces/capture-provider.d.ts.map +1 -0
  45. package/dist/interfaces/capture-provider.js +10 -0
  46. package/dist/interfaces/capture-provider.js.map +1 -0
  47. package/dist/interfaces/image-transformer.d.ts +7 -0
  48. package/dist/interfaces/image-transformer.d.ts.map +1 -0
  49. package/dist/interfaces/image-transformer.js +2 -0
  50. package/dist/interfaces/image-transformer.js.map +1 -0
  51. package/dist/interfaces/index.d.ts +7 -0
  52. package/dist/interfaces/index.d.ts.map +1 -0
  53. package/dist/interfaces/index.js +5 -0
  54. package/dist/interfaces/index.js.map +1 -0
  55. package/dist/interfaces/motion-analyzer.d.ts +55 -0
  56. package/dist/interfaces/motion-analyzer.d.ts.map +1 -0
  57. package/dist/interfaces/motion-analyzer.js +14 -0
  58. package/dist/interfaces/motion-analyzer.js.map +1 -0
  59. package/dist/kinds/capture.d.ts +58 -0
  60. package/dist/kinds/capture.d.ts.map +1 -0
  61. package/dist/kinds/capture.js +34 -0
  62. package/dist/kinds/capture.js.map +1 -0
  63. package/dist/kinds/image-transform.d.ts +91 -0
  64. package/dist/kinds/image-transform.d.ts.map +1 -0
  65. package/dist/kinds/image-transform.js +12 -0
  66. package/dist/kinds/image-transform.js.map +1 -0
  67. package/dist/kinds/index.d.ts +10 -0
  68. package/dist/kinds/index.d.ts.map +1 -0
  69. package/dist/kinds/index.js +7 -0
  70. package/dist/kinds/index.js.map +1 -0
  71. package/dist/kinds/motion.d.ts +57 -0
  72. package/dist/kinds/motion.d.ts.map +1 -0
  73. package/dist/kinds/motion.js +34 -0
  74. package/dist/kinds/motion.js.map +1 -0
  75. package/dist/media.plugin.d.ts +23 -0
  76. package/dist/media.plugin.d.ts.map +1 -0
  77. package/dist/media.plugin.js +48 -0
  78. package/dist/media.plugin.js.map +1 -0
  79. package/dist/schemas/artifact-reference.d.ts +59 -0
  80. package/dist/schemas/artifact-reference.d.ts.map +1 -0
  81. package/dist/schemas/artifact-reference.js +77 -0
  82. package/dist/schemas/artifact-reference.js.map +1 -0
  83. package/dist/schemas/capture-descriptor.d.ts +71 -0
  84. package/dist/schemas/capture-descriptor.d.ts.map +1 -0
  85. package/dist/schemas/capture-descriptor.js +74 -0
  86. package/dist/schemas/capture-descriptor.js.map +1 -0
  87. package/dist/schemas/capture-options.d.ts +51 -0
  88. package/dist/schemas/capture-options.d.ts.map +1 -0
  89. package/dist/schemas/capture-options.js +68 -0
  90. package/dist/schemas/capture-options.js.map +1 -0
  91. package/dist/schemas/capture-parse.d.ts +10 -0
  92. package/dist/schemas/capture-parse.d.ts.map +1 -0
  93. package/dist/schemas/capture-parse.js +30 -0
  94. package/dist/schemas/capture-parse.js.map +1 -0
  95. package/dist/schemas/capture-state.d.ts +77 -0
  96. package/dist/schemas/capture-state.d.ts.map +1 -0
  97. package/dist/schemas/capture-state.js +25 -0
  98. package/dist/schemas/capture-state.js.map +1 -0
  99. package/dist/schemas/image-transform.d.ts +269 -0
  100. package/dist/schemas/image-transform.d.ts.map +1 -0
  101. package/dist/schemas/image-transform.js +161 -0
  102. package/dist/schemas/image-transform.js.map +1 -0
  103. package/dist/schemas/index.d.ts +20 -0
  104. package/dist/schemas/index.d.ts.map +1 -0
  105. package/dist/schemas/index.js +17 -0
  106. package/dist/schemas/index.js.map +1 -0
  107. package/dist/schemas/motion-analysis.d.ts +187 -0
  108. package/dist/schemas/motion-analysis.d.ts.map +1 -0
  109. package/dist/schemas/motion-analysis.js +213 -0
  110. package/dist/schemas/motion-analysis.js.map +1 -0
  111. package/dist/schemas/motion-descriptor.d.ts +53 -0
  112. package/dist/schemas/motion-descriptor.d.ts.map +1 -0
  113. package/dist/schemas/motion-descriptor.js +47 -0
  114. package/dist/schemas/motion-descriptor.js.map +1 -0
  115. package/dist/schemas/motion-frame.d.ts +63 -0
  116. package/dist/schemas/motion-frame.d.ts.map +1 -0
  117. package/dist/schemas/motion-frame.js +86 -0
  118. package/dist/schemas/motion-frame.js.map +1 -0
  119. package/dist/schemas/opaque-id.d.ts +10 -0
  120. package/dist/schemas/opaque-id.d.ts.map +1 -0
  121. package/dist/schemas/opaque-id.js +24 -0
  122. package/dist/schemas/opaque-id.js.map +1 -0
  123. package/dist/schemas/sharing-state.d.ts +43 -0
  124. package/dist/schemas/sharing-state.d.ts.map +1 -0
  125. package/dist/schemas/sharing-state.js +11 -0
  126. package/dist/schemas/sharing-state.js.map +1 -0
  127. package/dist/svelte/CaptureLoader.svelte +29 -0
  128. package/dist/svelte/CaptureLoader.svelte.d.ts +11 -0
  129. package/dist/svelte/CaptureLoader.svelte.d.ts.map +1 -0
  130. package/dist/svelte/CapturePanel.svelte +189 -0
  131. package/dist/svelte/CapturePanel.svelte.d.ts +12 -0
  132. package/dist/svelte/CapturePanel.svelte.d.ts.map +1 -0
  133. package/dist/svelte/MediaUploader.svelte +109 -0
  134. package/dist/svelte/MediaUploader.svelte.d.ts +5 -0
  135. package/dist/svelte/MediaUploader.svelte.d.ts.map +1 -0
  136. package/dist/svelte/capture-parse.d.ts +2 -0
  137. package/dist/svelte/capture-parse.d.ts.map +1 -0
  138. package/dist/svelte/capture-parse.js +1 -0
  139. package/dist/svelte/failure-message.d.ts +2 -0
  140. package/dist/svelte/failure-message.d.ts.map +1 -0
  141. package/dist/svelte/failure-message.js +1 -0
  142. package/dist/svelte/index.d.ts +8 -0
  143. package/dist/svelte/index.d.ts.map +1 -0
  144. package/dist/svelte/index.js +6 -0
  145. package/dist/svelte/runtime-log.d.ts +14 -0
  146. package/dist/svelte/runtime-log.d.ts.map +1 -0
  147. package/dist/svelte/runtime-log.js +19 -0
  148. package/dist/svelte/runtime.d.ts +9 -0
  149. package/dist/svelte/runtime.d.ts.map +1 -0
  150. package/dist/svelte/runtime.js +27 -0
  151. package/dist/svelte/sharing/README.md +38 -0
  152. package/dist/svelte/sharing/components/ShareButton.svelte +57 -0
  153. package/dist/svelte/sharing/components/ShareButton.svelte.d.ts +11 -0
  154. package/dist/svelte/sharing/components/ShareButton.svelte.d.ts.map +1 -0
  155. package/dist/svelte/sharing/components/ShareDialog.svelte +222 -0
  156. package/dist/svelte/sharing/components/ShareDialog.svelte.d.ts +8 -0
  157. package/dist/svelte/sharing/components/ShareDialog.svelte.d.ts.map +1 -0
  158. package/dist/svelte/sharing/components/ShareLoader.svelte +19 -0
  159. package/dist/svelte/sharing/components/ShareLoader.svelte.d.ts +7 -0
  160. package/dist/svelte/sharing/components/ShareLoader.svelte.d.ts.map +1 -0
  161. package/dist/svelte/sharing/index.d.ts +20 -0
  162. package/dist/svelte/sharing/index.d.ts.map +1 -0
  163. package/dist/svelte/sharing/index.js +19 -0
  164. package/dist/svelte/sharing/runtime.d.ts +44 -0
  165. package/dist/svelte/sharing/runtime.d.ts.map +1 -0
  166. package/dist/svelte/sharing/runtime.js +81 -0
  167. package/dist/svelte/sharing/state.svelte.d.ts +28 -0
  168. package/dist/svelte/sharing/state.svelte.d.ts.map +1 -0
  169. package/dist/svelte/sharing/state.svelte.js +101 -0
  170. package/dist/svelte/sharing/styles.css +8 -0
  171. package/dist/svelte/state.svelte.d.ts +38 -0
  172. package/dist/svelte/state.svelte.d.ts.map +1 -0
  173. package/dist/svelte/state.svelte.js +102 -0
  174. package/dist/svelte/styles.css +2 -0
  175. package/dist/svelte/uploader.d.ts +17 -0
  176. package/dist/svelte/uploader.d.ts.map +1 -0
  177. package/dist/svelte/uploader.js +9 -0
  178. package/dist/uploads/adapters/local/index.d.ts +9 -0
  179. package/dist/uploads/adapters/local/index.d.ts.map +1 -0
  180. package/dist/uploads/adapters/local/index.js +12 -0
  181. package/dist/uploads/adapters/local/index.js.map +1 -0
  182. package/dist/uploads/adapters/local/local-storage.descriptor.d.ts +24 -0
  183. package/dist/uploads/adapters/local/local-storage.descriptor.d.ts.map +1 -0
  184. package/dist/uploads/adapters/local/local-storage.descriptor.js +20 -0
  185. package/dist/uploads/adapters/local/local-storage.descriptor.js.map +1 -0
  186. package/dist/uploads/adapters/local/local-storage.impl.d.ts +123 -0
  187. package/dist/uploads/adapters/local/local-storage.impl.d.ts.map +1 -0
  188. package/dist/uploads/adapters/local/local-storage.impl.js +231 -0
  189. package/dist/uploads/adapters/local/local-storage.impl.js.map +1 -0
  190. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.d.ts +9 -0
  191. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.d.ts.map +1 -0
  192. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.js +41 -0
  193. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.js.map +1 -0
  194. package/dist/uploads/client/helper.d.ts +26 -0
  195. package/dist/uploads/client/helper.d.ts.map +1 -0
  196. package/dist/uploads/client/helper.js +27 -0
  197. package/dist/uploads/client/helper.js.map +1 -0
  198. package/dist/uploads/client/index.d.ts +11 -0
  199. package/dist/uploads/client/index.d.ts.map +1 -0
  200. package/dist/uploads/client/index.js +9 -0
  201. package/dist/uploads/client/index.js.map +1 -0
  202. package/dist/uploads/client/sharing-client.d.ts +45 -0
  203. package/dist/uploads/client/sharing-client.d.ts.map +1 -0
  204. package/dist/uploads/client/sharing-client.js +153 -0
  205. package/dist/uploads/client/sharing-client.js.map +1 -0
  206. package/dist/uploads/client/uploads-client.d.ts +200 -0
  207. package/dist/uploads/client/uploads-client.d.ts.map +1 -0
  208. package/dist/uploads/client/uploads-client.js +487 -0
  209. package/dist/uploads/client/uploads-client.js.map +1 -0
  210. package/dist/uploads/docs/uploads.docs.descriptor.d.ts +4 -0
  211. package/dist/uploads/docs/uploads.docs.descriptor.d.ts.map +1 -0
  212. package/dist/uploads/docs/uploads.docs.descriptor.js +65 -0
  213. package/dist/uploads/docs/uploads.docs.descriptor.js.map +1 -0
  214. package/dist/uploads/docs/uploads.share-links.docs.descriptor.d.ts +22 -0
  215. package/dist/uploads/docs/uploads.share-links.docs.descriptor.d.ts.map +1 -0
  216. package/dist/uploads/docs/uploads.share-links.docs.descriptor.js +86 -0
  217. package/dist/uploads/docs/uploads.share-links.docs.descriptor.js.map +1 -0
  218. package/dist/uploads/index.d.ts +12 -0
  219. package/dist/uploads/index.d.ts.map +1 -0
  220. package/dist/uploads/index.js +13 -0
  221. package/dist/uploads/index.js.map +1 -0
  222. package/dist/uploads/schemas/common.d.ts +135 -0
  223. package/dist/uploads/schemas/common.d.ts.map +1 -0
  224. package/dist/uploads/schemas/common.js +152 -0
  225. package/dist/uploads/schemas/common.js.map +1 -0
  226. package/dist/uploads/schemas/forms.d.ts +59 -0
  227. package/dist/uploads/schemas/forms.d.ts.map +1 -0
  228. package/dist/uploads/schemas/forms.js +41 -0
  229. package/dist/uploads/schemas/forms.js.map +1 -0
  230. package/dist/uploads/schemas/index.d.ts +18 -0
  231. package/dist/uploads/schemas/index.d.ts.map +1 -0
  232. package/dist/uploads/schemas/index.js +13 -0
  233. package/dist/uploads/schemas/index.js.map +1 -0
  234. package/dist/uploads/schemas/records.d.ts +81 -0
  235. package/dist/uploads/schemas/records.d.ts.map +1 -0
  236. package/dist/uploads/schemas/records.js +82 -0
  237. package/dist/uploads/schemas/records.js.map +1 -0
  238. package/dist/uploads/schemas/requests.d.ts +32 -0
  239. package/dist/uploads/schemas/requests.d.ts.map +1 -0
  240. package/dist/uploads/schemas/requests.js +30 -0
  241. package/dist/uploads/schemas/requests.js.map +1 -0
  242. package/dist/uploads/schemas/shares.d.ts +67 -0
  243. package/dist/uploads/schemas/shares.d.ts.map +1 -0
  244. package/dist/uploads/schemas/shares.js +39 -0
  245. package/dist/uploads/schemas/shares.js.map +1 -0
  246. package/dist/uploads/server/backend.d.ts +8 -0
  247. package/dist/uploads/server/backend.d.ts.map +1 -0
  248. package/dist/uploads/server/backend.js +11 -0
  249. package/dist/uploads/server/backend.js.map +1 -0
  250. package/dist/uploads/server/config.d.ts +5 -0
  251. package/dist/uploads/server/config.d.ts.map +1 -0
  252. package/dist/uploads/server/config.js +38 -0
  253. package/dist/uploads/server/config.js.map +1 -0
  254. package/dist/uploads/server/service.d.ts +77 -0
  255. package/dist/uploads/server/service.d.ts.map +1 -0
  256. package/dist/uploads/server/service.js +152 -0
  257. package/dist/uploads/server/service.js.map +1 -0
  258. package/dist/uploads/server/storage-client.d.ts +11 -0
  259. package/dist/uploads/server/storage-client.d.ts.map +1 -0
  260. package/dist/uploads/server/storage-client.js +23 -0
  261. package/dist/uploads/server/storage-client.js.map +1 -0
  262. package/dist/uploads/server/storage.d.ts +40 -0
  263. package/dist/uploads/server/storage.d.ts.map +1 -0
  264. package/dist/uploads/server/storage.js +59 -0
  265. package/dist/uploads/server/storage.js.map +1 -0
  266. package/dist/uploads/uploads.plugin.d.ts +11 -0
  267. package/dist/uploads/uploads.plugin.d.ts.map +1 -0
  268. package/dist/uploads/uploads.plugin.js +89 -0
  269. package/dist/uploads/uploads.plugin.js.map +1 -0
  270. package/dist/utils/attempt.d.ts +27 -0
  271. package/dist/utils/attempt.d.ts.map +1 -0
  272. package/dist/utils/attempt.js +39 -0
  273. package/dist/utils/attempt.js.map +1 -0
  274. package/dist/utils/failure-message.d.ts +8 -0
  275. package/dist/utils/failure-message.d.ts.map +1 -0
  276. package/dist/utils/failure-message.js +28 -0
  277. package/dist/utils/failure-message.js.map +1 -0
  278. package/package.json +257 -4
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Logging for the Svelte surface: a component or store logs on the runtime it
3
+ * was handed, never the host default. The Svelte runtime store a component
4
+ * captured at init is the source: its `load()` yields the runtime the logger
5
+ * binds to.
6
+ */
7
+ import { getLogger } from '@vibesdotdev/logging';
8
+ import { attempt } from "../utils/attempt.js";
9
+ /**
10
+ * Log one entry on the runtime `source` holds. A source that never loaded has
11
+ * no runtime to bind a logger to, so the entry is dropped; the store records
12
+ * that failed load on its own `error`.
13
+ */
14
+ export async function logOn(source, level, context, message, fields) {
15
+ const loaded = await attempt(() => source.load());
16
+ if (!loaded.ok)
17
+ return;
18
+ getLogger('media', loaded.value).child({ context })[level](message, fields);
19
+ }
@@ -0,0 +1,9 @@
1
+ export declare function captureMimeType(format: string): string;
2
+ /**
3
+ * A portable timestamped file name for a capture. The extension comes from the
4
+ * format only when it is a short lowercase token; any other format (or an
5
+ * unreadable timestamp) yields `bin` / `unknown-time`, so a provider-supplied
6
+ * format can never shape the name beyond its extension.
7
+ */
8
+ export declare function captureFileName(timestamp: number, format: string): string;
9
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/svelte/runtime.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOtD;AAKD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKzE"}
@@ -0,0 +1,27 @@
1
+ export function captureMimeType(format) {
2
+ if (format === 'jpeg' || format === 'jpg')
3
+ return 'image/jpeg';
4
+ if (format === 'png')
5
+ return 'image/png';
6
+ if (format === 'webm')
7
+ return 'video/webm';
8
+ if (format === 'mp4')
9
+ return 'video/mp4';
10
+ if (format === 'wav')
11
+ return 'audio/wav';
12
+ return 'application/octet-stream';
13
+ }
14
+ /** A file extension a capture's format may become: short and alphanumeric, anything else is `bin`. */
15
+ const EXTENSION_FORMAT = /^[a-z0-9]{1,8}$/;
16
+ /**
17
+ * A portable timestamped file name for a capture. The extension comes from the
18
+ * format only when it is a short lowercase token; any other format (or an
19
+ * unreadable timestamp) yields `bin` / `unknown-time`, so a provider-supplied
20
+ * format can never shape the name beyond its extension.
21
+ */
22
+ export function captureFileName(timestamp, format) {
23
+ const extension = format === 'jpeg' ? 'jpg' : EXTENSION_FORMAT.test(format) ? format : 'bin';
24
+ const moment = new Date(timestamp);
25
+ const stamp = Number.isNaN(moment.getTime()) ? 'unknown-time' : moment.toISOString().replace(/[:.]/g, '-');
26
+ return `vibes-capture-${stamp}.${extension}`;
27
+ }
@@ -0,0 +1,38 @@
1
+ # @vibesdotdev/media/svelte/sharing
2
+
3
+ Reusable Svelte 5 components for revocable media share links.
4
+
5
+ ```svelte
6
+ <script lang="ts">
7
+ // Import via narrow subpaths to avoid pulling the $state barrel into .ts modules.
8
+ import ShareButton from '@vibesdotdev/media/svelte/sharing/components/ShareButton.svelte';
9
+ import ShareLoader from '@vibesdotdev/media/svelte/sharing/components/ShareLoader.svelte';
10
+
11
+ let { fileId }: { fileId: string } = $props();
12
+ </script>
13
+
14
+ <ShareLoader {fileId} />
15
+ <ShareButton {fileId} />
16
+ ```
17
+
18
+ `<ShareButton>` opens a dialog where the media owner can create a revocable
19
+ public link (optional label, expiry, and download cap), copy it to the
20
+ clipboard, and revoke it. Links resolve through the uploads `runtime/client`
21
+ (`getVibesUploadsClient(runtime)`); the caller must compose
22
+ `@vibesdotdev/media/uploads/plugin` into the runtime first.
23
+
24
+ The plugin also registers `uploads.sharing`, the runtime-owned shared state.
25
+ Each component owns a reactive projection and releases its subscription when
26
+ unmounted. Removing one consumer leaves the others active; disposing the runtime
27
+ clears shared state and ignores late HTTP completions, including revoke rollback.
28
+ `SharingState` and `getSharingState()` remain compatible entrypoints.
29
+
30
+ Active badges count future and never-expiring links, excluding revoked, expired,
31
+ and invalid-expiry links. Links appear newest first. Mounted lifecycle proof runs
32
+ under `bun run test:svelte` with real runtime clients and HTTP/clock boundaries.
33
+
34
+ The owner is billed for anonymous share-download egress, so the download cap is
35
+ a cost ceiling. Revocation is immediate at the origin (a CDN copy may persist
36
+ until its short cache TTL expires).
37
+
38
+ See [`SPEC.md`](./SPEC.md) for the full contract.
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import { Icon } from '@vibesdotdev/kit/components/icon';
3
+ import { Button } from '@vibesdotdev/kit/components/ui/button';
4
+ import { getSharingState } from '../state.svelte.js';
5
+ import ShareDialog from './ShareDialog.svelte';
6
+
7
+ /**
8
+ * ShareButton — an icon button that opens the share-link dialog for a media
9
+ * item. Reuses the kit Button/Icon to match the host app's button row (e.g.
10
+ * the ImageViewer download button). Reflects the active link count as a small
11
+ * badge when known.
12
+ */
13
+ let {
14
+ fileId,
15
+ size = 'sm',
16
+ variant = 'outline',
17
+ label = 'Share image',
18
+ ariaLabel
19
+ }: {
20
+ fileId: string;
21
+ size?: 'default' | 'sm' | 'lg' | 'icon';
22
+ variant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link' | 'destructive';
23
+ label?: string;
24
+ ariaLabel?: string;
25
+ } = $props();
26
+
27
+ let open = $state(false);
28
+
29
+ const sharingState = getSharingState();
30
+ const activeCount = $derived(sharingState.activeLinks(fileId).length);
31
+ const activeLinkText = $derived(
32
+ activeCount === 0
33
+ ? ''
34
+ : activeCount === 1
35
+ ? ', 1 active share link'
36
+ : `, ${activeCount} active share links`
37
+ );
38
+ const buttonLabel = $derived(`${ariaLabel ?? (label || 'Open share links')}${activeLinkText}`);
39
+ </script>
40
+
41
+ <Button
42
+ type="button"
43
+ {size}
44
+ {variant}
45
+ onclick={() => (open = true)}
46
+ aria-label={buttonLabel}
47
+ aria-haspopup="dialog"
48
+ aria-expanded={open}
49
+ disabled={!fileId}
50
+ >
51
+ <Icon icon="lucide:share-2" class="h-4 w-4" aria-hidden="true" />
52
+ {#if activeCount > 0}
53
+ <span class="ml-1 text-xs tabular-nums" aria-hidden="true">{activeCount}</span>
54
+ {/if}
55
+ </Button>
56
+
57
+ <ShareDialog {fileId} bind:open />
@@ -0,0 +1,11 @@
1
+ type $$ComponentProps = {
2
+ fileId: string;
3
+ size?: 'default' | 'sm' | 'lg' | 'icon';
4
+ variant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link' | 'destructive';
5
+ label?: string;
6
+ ariaLabel?: string;
7
+ };
8
+ declare const ShareButton: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type ShareButton = ReturnType<typeof ShareButton>;
10
+ export default ShareButton;
11
+ //# sourceMappingURL=ShareButton.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShareButton.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/sharing/components/ShareButton.svelte.ts"],"names":[],"mappings":"AAQC,KAAK,gBAAgB,GAAI;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IACxC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AA8CH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,222 @@
1
+ <script lang="ts">
2
+ import { Icon } from '@vibesdotdev/kit/components/icon';
3
+ import { Badge } from '@vibesdotdev/kit/components/ui/badge';
4
+ import { Button } from '@vibesdotdev/kit/components/ui/button';
5
+ import * as Dialog from '@vibesdotdev/kit/components/ui/dialog';
6
+ import { Input } from '@vibesdotdev/kit/components/ui/input';
7
+ import { Label } from '@vibesdotdev/kit/components/ui/label';
8
+ import { toast } from 'svelte-sonner';
9
+ import { getRuntimeSvelteContext } from '@vibesdotdev/surface-sveltekit/svelte';
10
+ import { logOn } from '../../runtime-log.js';
11
+ import { attempt, failureLabel } from '../../../utils/attempt.js';
12
+ import { failureMessage } from '../../failure-message.js';
13
+ import { getSharingState } from '../state.svelte.js';
14
+ import {
15
+ SHARE_EXPIRY_PRESETS,
16
+ formatExpiry,
17
+ isLinkActive,
18
+ copyToClipboard,
19
+ type ClipboardHost
20
+ } from '../runtime.js';
21
+
22
+ /**
23
+ * ShareDialog — create, copy, and revoke revocable public share links for a
24
+ * media item. All mutations go through `sharingState`, which resolves the
25
+ * uploads `runtime/client`. The owner is charged for anonymous share egress,
26
+ * so an optional max-downloads cap is offered as a cost guard.
27
+ */
28
+ let {
29
+ fileId,
30
+ open = $bindable(false)
31
+ }: {
32
+ fileId: string;
33
+ open?: boolean;
34
+ } = $props();
35
+
36
+ // Captured at component init: Svelte context is unavailable after mount.
37
+ const runtimeStore = getRuntimeSvelteContext();
38
+ /** The page's own clipboard and document; a failed attempt is logged by class only. */
39
+ const clipboardHost: ClipboardHost = {
40
+ navigator: globalThis.navigator,
41
+ document: globalThis.document,
42
+ onFailure: (failure) =>
43
+ void logOn(runtimeStore, 'debug', 'svelte.sharing', 'share dialog: clipboard attempt failed', { failure: failureLabel(failure) })
44
+ };
45
+
46
+ let label = $state('');
47
+ let expiryValue = $state<number | null>(null);
48
+ let maxDownloads = $state<number | null>(null);
49
+ let creating = $state(false);
50
+
51
+ const sharingState = getSharingState();
52
+ const links = $derived(sharingState.links(fileId));
53
+ const loading = $derived(sharingState.isLoading(fileId));
54
+ const loadError = $derived(sharingState.error(fileId));
55
+
56
+ // Lazily load existing links when the dialog first opens.
57
+ $effect(() => {
58
+ if (open && fileId) void sharingState.init(fileId);
59
+ });
60
+
61
+ function resetForm() {
62
+ label = '';
63
+ expiryValue = null;
64
+ maxDownloads = null;
65
+ }
66
+
67
+ async function createLink() {
68
+ if (!fileId || creating) return;
69
+ creating = true;
70
+ const created = await attempt(() =>
71
+ sharingState.create(fileId, {
72
+ label: label.trim() || undefined,
73
+ expiresIn: expiryValue ?? undefined,
74
+ maxDownloads: maxDownloads ?? undefined
75
+ })
76
+ );
77
+ creating = false;
78
+ if (!created.ok) {
79
+ // A fixed message; the failure's class and reason are logged, never its text.
80
+ toast.error(failureMessage(created.failure, 'Failed to create share link'));
81
+ void logOn(runtimeStore, 'warn', 'svelte.sharing', 'share dialog: create failed', { failure: failureLabel(created.failure) });
82
+ return;
83
+ }
84
+ resetForm();
85
+ const ok = await copyToClipboard(created.value.shareUrl, clipboardHost);
86
+ toast.success(ok ? 'Share link created and copied' : 'Share link created');
87
+ }
88
+
89
+ async function copyLink(url: string) {
90
+ const ok = await copyToClipboard(url, clipboardHost);
91
+ if (ok) toast.success('Link copied');
92
+ else toast.error('Could not copy link');
93
+ }
94
+
95
+ async function revokeLink(shareId: string) {
96
+ const revoked = await attempt(() => sharingState.revoke(fileId, shareId));
97
+ if (!revoked.ok) {
98
+ toast.error(failureMessage(revoked.failure, 'Failed to revoke share link'));
99
+ void logOn(runtimeStore, 'warn', 'svelte.sharing', 'share dialog: revoke failed', { failure: failureLabel(revoked.failure) });
100
+ return;
101
+ }
102
+ if (revoked.value) toast.success('Share link revoked');
103
+ else toast.error('Share link not found');
104
+ }
105
+
106
+ function onExpiryChange(raw: string) {
107
+ expiryValue = raw === 'null' ? null : Number(raw);
108
+ }
109
+
110
+ function onMaxDownloadsInput(raw: string) {
111
+ const n = Number(raw);
112
+ maxDownloads = raw && Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
113
+ }
114
+ </script>
115
+
116
+ <Dialog.Root bind:open>
117
+ <Dialog.Content class="sm:max-w-lg">
118
+ <Dialog.Header>
119
+ <Dialog.Title>Share link</Dialog.Title>
120
+ <Dialog.Description>
121
+ Create a revocable public link to this media. Anyone with the link can view it until you
122
+ revoke it. You are charged for downloads, so set a download cap if you want a cost ceiling.
123
+ </Dialog.Description>
124
+ </Dialog.Header>
125
+
126
+ <div class="space-y-4 py-2">
127
+ <div class="space-y-2">
128
+ <Label for="share-label">Label (optional)</Label>
129
+ <Input id="share-label" bind:value={label} placeholder="e.g. Twitter" />
130
+ </div>
131
+
132
+ <div class="grid grid-cols-2 gap-3">
133
+ <div class="space-y-2">
134
+ <Label for="share-expiry">Expires</Label>
135
+ <select
136
+ id="share-expiry"
137
+ class="border-input bg-background ring-offset-background focus-visible:ring-ring flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1"
138
+ value={expiryValue === null ? 'null' : String(expiryValue)}
139
+ onchange={(event) => onExpiryChange(event.currentTarget.value)}
140
+ >
141
+ {#each SHARE_EXPIRY_PRESETS as preset}
142
+ <option value={preset.value === null ? 'null' : String(preset.value)}>
143
+ {preset.label}
144
+ </option>
145
+ {/each}
146
+ </select>
147
+ </div>
148
+ <div class="space-y-2">
149
+ <Label for="share-max">Max downloads (optional)</Label>
150
+ <Input
151
+ id="share-max"
152
+ type="number"
153
+ min="1"
154
+ placeholder="Unlimited"
155
+ value={maxDownloads ?? ''}
156
+ oninput={(event) => onMaxDownloadsInput(event.currentTarget.value)}
157
+ />
158
+ </div>
159
+ </div>
160
+
161
+ <Button onclick={createLink} disabled={!fileId || creating} class="w-full">
162
+ <Icon icon="lucide:link" class="mr-2 h-4 w-4" />
163
+ {creating ? 'Creating…' : 'Create share link'}
164
+ </Button>
165
+
166
+ <div class="space-y-2">
167
+ <div class="text-muted-foreground text-xs font-medium">Existing links</div>
168
+ {#if loading}
169
+ <p class="text-muted-foreground text-sm">Loading…</p>
170
+ {:else if loadError}
171
+ <p class="text-destructive text-sm">{loadError}</p>
172
+ {:else if links.length === 0}
173
+ <p class="text-muted-foreground text-sm">No share links yet.</p>
174
+ {:else}
175
+ <ul class="space-y-2">
176
+ {#each links as link (link.id)}
177
+ {@const active = isLinkActive(link)}
178
+ <li class="flex items-center gap-2 rounded-md border p-2">
179
+ <div class="min-w-0 flex-1">
180
+ <div class="flex items-center gap-2">
181
+ <span class="truncate text-sm font-medium">
182
+ {link.label || 'Untitled link'}
183
+ </span>
184
+ <Badge variant={active ? 'secondary' : 'outline'}>
185
+ {link.revokedAt ? 'Revoked' : active ? 'Active' : 'Expired'}
186
+ </Badge>
187
+ </div>
188
+ <div class="text-muted-foreground truncate text-xs">{link.shareUrl}</div>
189
+ <div class="text-muted-foreground text-xs">
190
+ Expires: {formatExpiry(link.expiresAt)} · Downloads: {link.downloadCount}{#if link.maxDownloads}/{link.maxDownloads}{/if}
191
+ </div>
192
+ </div>
193
+ {#if active}
194
+ <Button
195
+ size="sm"
196
+ variant="ghost"
197
+ aria-label="Copy link"
198
+ onclick={() => copyLink(link.shareUrl)}
199
+ >
200
+ <Icon icon="lucide:copy" class="h-4 w-4" />
201
+ </Button>
202
+ <Button
203
+ size="sm"
204
+ variant="ghost"
205
+ aria-label="Revoke link"
206
+ onclick={() => revokeLink(link.id)}
207
+ >
208
+ <Icon icon="lucide:trash-2" class="h-4 w-4" />
209
+ </Button>
210
+ {/if}
211
+ </li>
212
+ {/each}
213
+ </ul>
214
+ {/if}
215
+ </div>
216
+ </div>
217
+
218
+ <Dialog.Footer>
219
+ <Button variant="outline" onclick={() => (open = false)}>Close</Button>
220
+ </Dialog.Footer>
221
+ </Dialog.Content>
222
+ </Dialog.Root>
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ fileId: string;
3
+ open?: boolean;
4
+ };
5
+ declare const ShareDialog: import("svelte").Component<$$ComponentProps, {}, "open">;
6
+ type ShareDialog = ReturnType<typeof ShareDialog>;
7
+ export default ShareDialog;
8
+ //# sourceMappingURL=ShareDialog.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShareDialog.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/sharing/components/ShareDialog.svelte.ts"],"names":[],"mappings":"AAuBC,KAAK,gBAAgB,GAAI;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAmMH,QAAA,MAAM,WAAW,0DAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { getSharingState } from '../state.svelte.js';
4
+
5
+ /**
6
+ * ShareLoader — non-visual. Eagerly loads a file's existing share links into
7
+ * the runtime's sharing state so `<ShareButton>` can reflect link count
8
+ * immediately. Mount once per file (or per panel). Renders nothing.
9
+ *
10
+ * The dialog also lazily inits on open, so this loader is optional — use it
11
+ * when you want the link count badge to be correct before the dialog opens.
12
+ */
13
+ let { fileId }: { fileId: string } = $props();
14
+ const sharingState = getSharingState();
15
+
16
+ onMount(() => {
17
+ if (fileId) void sharingState.init(fileId);
18
+ });
19
+ </script>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ fileId: string;
3
+ };
4
+ declare const ShareLoader: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type ShareLoader = ReturnType<typeof ShareLoader>;
6
+ export default ShareLoader;
7
+ //# sourceMappingURL=ShareLoader.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShareLoader.svelte.d.ts","sourceRoot":"","sources":["../../../../src/svelte/sharing/components/ShareLoader.svelte.ts"],"names":[],"mappings":"AAMC,KAAK,gBAAgB,GAAI;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAuB7C,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @vibesdotdev/media/svelte/sharing
3
+ *
4
+ * Reusable Svelte components and runtime helpers for revocable share links over
5
+ * uploaded media. Any app can embed `<ShareButton>` / `<ShareDialog>` to let an
6
+ * owner create, copy, and revoke public share links for a media item, backed by
7
+ * the runtime-owned `uploads.sharing` client and its context-owned projections.
8
+ *
9
+ * NOTE: this barrel re-exports `state.svelte.ts`, which uses Svelte runes. Apps
10
+ * that import the barrel from a non-Svelte (`.ts`) module will pull the runes
11
+ * runtime. To avoid that, import components via their narrow subpaths
12
+ * (`@vibesdotdev/media/svelte/sharing/components/ShareButton.svelte`).
13
+ */
14
+ export { getSharingState, type SharingState } from './state.svelte.ts';
15
+ export { SHARE_EXPIRY_PRESETS, formatExpiry, isLinkActive, copyToClipboard } from './runtime.ts';
16
+ export { default as ShareButton } from './components/ShareButton.svelte';
17
+ export { default as ShareDialog } from './components/ShareDialog.svelte';
18
+ export { default as ShareLoader } from './components/ShareLoader.svelte';
19
+ export type { ShareLink, CreateShareLinkOptions } from '@vibesdotdev/media/uploads/client';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/svelte/sharing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEvE,OAAO,EACN,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEzE,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @vibesdotdev/media/svelte/sharing
3
+ *
4
+ * Reusable Svelte components and runtime helpers for revocable share links over
5
+ * uploaded media. Any app can embed `<ShareButton>` / `<ShareDialog>` to let an
6
+ * owner create, copy, and revoke public share links for a media item, backed by
7
+ * the runtime-owned `uploads.sharing` client and its context-owned projections.
8
+ *
9
+ * NOTE: this barrel re-exports `state.svelte.ts`, which uses Svelte runes. Apps
10
+ * that import the barrel from a non-Svelte (`.ts`) module will pull the runes
11
+ * runtime. To avoid that, import components via their narrow subpaths
12
+ * (`@vibesdotdev/media/svelte/sharing/components/ShareButton.svelte`).
13
+ */
14
+ export { getSharingState } from "./state.svelte.js";
15
+ export { SHARE_EXPIRY_PRESETS, formatExpiry, isLinkActive, copyToClipboard } from "./runtime.js";
16
+ export { default as ShareButton } from './components/ShareButton.svelte';
17
+ export { default as ShareDialog } from './components/ShareDialog.svelte';
18
+ export { default as ShareLoader } from './components/ShareLoader.svelte';
19
+ // Type-only re-export; the uploads client is composed at the app surface.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Browser runtime helpers for `@vibesdotdev/media/svelte/sharing`.
3
+ *
4
+ * Rune-free, side-effect-free helpers used by the components and unit-tested
5
+ * directly (the state store lives in state.svelte.ts and is not imported here,
6
+ * to keep this module importable from non-Svelte contexts and tests).
7
+ */
8
+ import type { CaughtFailure } from '@vibesdotdev/kernel';
9
+ /**
10
+ * What `copyToClipboard` copies through: a window's navigator and document
11
+ * (the page's own by default), and where a failed attempt is reported. This
12
+ * module logs nothing itself, so it stays usable with no runtime booted.
13
+ */
14
+ export interface ClipboardHost {
15
+ navigator?: Pick<Navigator, 'clipboard'>;
16
+ document?: Document;
17
+ onFailure?: (failure: CaughtFailure) => void;
18
+ }
19
+ /** Expiry option presets offered by the share dialog (seconds, or null=never). */
20
+ export declare const SHARE_EXPIRY_PRESETS: Array<{
21
+ label: string;
22
+ value: number | null;
23
+ }>;
24
+ /**
25
+ * Format an ISO expiry timestamp (or null) into a short human label. Returns
26
+ * 'Never' for null, 'Expired' for past timestamps and 'Unknown' for a value
27
+ * that is not a timestamp: it is never read as "never expires".
28
+ */
29
+ export declare function formatExpiry(expiresAt: string | null, now?: number): string;
30
+ /**
31
+ * True when the share link is currently usable: not revoked and not expired.
32
+ * Fails closed: an expiry that is not a timestamp is not active.
33
+ */
34
+ export declare function isLinkActive(link: {
35
+ revokedAt: string | null;
36
+ expiresAt: string | null;
37
+ }, now?: number): boolean;
38
+ /**
39
+ * Copy text to the clipboard. Uses the async Clipboard API when available and
40
+ * falls back to a hidden textarea + execCommand for older/insecure contexts.
41
+ * Returns true on success.
42
+ */
43
+ export declare function copyToClipboard(text: string, host?: ClipboardHost): Promise<boolean>;
44
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/svelte/sharing/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;CAC7C;AAED,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAM/E,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,CAMvF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC5D,GAAG,GAAE,MAAmB,GACtB,OAAO,CAMT;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAiC1F"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Browser runtime helpers for `@vibesdotdev/media/svelte/sharing`.
3
+ *
4
+ * Rune-free, side-effect-free helpers used by the components and unit-tested
5
+ * directly (the state store lives in state.svelte.ts and is not imported here,
6
+ * to keep this module importable from non-Svelte contexts and tests).
7
+ */
8
+ import { attempt, attemptSync } from "../../utils/attempt.js";
9
+ /** Expiry option presets offered by the share dialog (seconds, or null=never). */
10
+ export const SHARE_EXPIRY_PRESETS = [
11
+ { label: 'Never', value: null },
12
+ { label: '1 hour', value: 60 * 60 },
13
+ { label: '24 hours', value: 60 * 60 * 24 },
14
+ { label: '7 days', value: 60 * 60 * 24 * 7 },
15
+ { label: '30 days', value: 60 * 60 * 24 * 30 }
16
+ ];
17
+ /**
18
+ * Format an ISO expiry timestamp (or null) into a short human label. Returns
19
+ * 'Never' for null, 'Expired' for past timestamps and 'Unknown' for a value
20
+ * that is not a timestamp: it is never read as "never expires".
21
+ */
22
+ export function formatExpiry(expiresAt, now = Date.now()) {
23
+ if (expiresAt === null)
24
+ return 'Never';
25
+ const ts = Date.parse(expiresAt);
26
+ if (Number.isNaN(ts))
27
+ return 'Unknown';
28
+ if (ts <= now)
29
+ return 'Expired';
30
+ return new Date(ts).toLocaleString();
31
+ }
32
+ /**
33
+ * True when the share link is currently usable: not revoked and not expired.
34
+ * Fails closed: an expiry that is not a timestamp is not active.
35
+ */
36
+ export function isLinkActive(link, now = Date.now()) {
37
+ if (link.revokedAt !== null)
38
+ return false;
39
+ if (link.expiresAt === null)
40
+ return true;
41
+ const ts = Date.parse(link.expiresAt);
42
+ if (Number.isNaN(ts))
43
+ return false;
44
+ return ts > now;
45
+ }
46
+ /**
47
+ * Copy text to the clipboard. Uses the async Clipboard API when available and
48
+ * falls back to a hidden textarea + execCommand for older/insecure contexts.
49
+ * Returns true on success.
50
+ */
51
+ export async function copyToClipboard(text, host) {
52
+ // The page's own navigator and document unless the caller names the host (another window, a test).
53
+ const { navigator: pageNavigator, document: pageDocument, onFailure } = host ?? { navigator: globalThis.navigator, document: globalThis.document };
54
+ const clipboard = pageNavigator?.clipboard;
55
+ if (clipboard) {
56
+ const written = await attempt(() => clipboard.writeText(text));
57
+ if (written.ok)
58
+ return true;
59
+ // The Clipboard API was denied or failed: the caller is told, and the execCommand path is tried.
60
+ onFailure?.(written.failure);
61
+ }
62
+ if (!pageDocument)
63
+ return false;
64
+ const copied = attemptSync(() => {
65
+ const textarea = pageDocument.createElement('textarea');
66
+ textarea.value = text;
67
+ textarea.setAttribute('readonly', '');
68
+ textarea.style.position = 'absolute';
69
+ textarea.style.left = '-9999px';
70
+ pageDocument.body.appendChild(textarea);
71
+ textarea.select();
72
+ const ok = pageDocument.execCommand('copy');
73
+ pageDocument.body.removeChild(textarea);
74
+ return ok;
75
+ });
76
+ if (copied.ok)
77
+ return copied.value;
78
+ // execCommand is a deprecated browser API; its failure is `false` to the caller, who is also told why.
79
+ onFailure?.(copied.failure);
80
+ return false;
81
+ }
@@ -0,0 +1,28 @@
1
+ import { type VibesRuntime } from '@vibesdotdev/kernel';
2
+ import type { CreateShareLinkOptions, ShareLink } from '../../uploads/schemas/shares.ts';
3
+ import { type SharingEntry } from '../../schemas/sharing-state.ts';
4
+ /** A context-owned reactive projection of the runtime's shared capability. */
5
+ export declare class SharingState {
6
+ private readonly loadRuntime;
7
+ private entries;
8
+ private pending;
9
+ private unsubscribe?;
10
+ private generation;
11
+ private disposed;
12
+ constructor(loadRuntime: () => Promise<VibesRuntime>);
13
+ entry(fileId: string): SharingEntry;
14
+ links(fileId: string): ShareLink[];
15
+ isLoading(fileId: string): boolean;
16
+ error(fileId: string): string | null;
17
+ activeLinks(fileId: string): ShareLink[];
18
+ private resolveClient;
19
+ private assertCurrent;
20
+ init(fileId: string): Promise<void>;
21
+ refresh(fileId: string): Promise<void>;
22
+ create(fileId: string, options?: CreateShareLinkOptions): Promise<ShareLink>;
23
+ revoke(fileId: string, shareId: string): Promise<boolean>;
24
+ dispose(): void;
25
+ }
26
+ /** Each consumer releases its projection; the runtime retains shared observations. */
27
+ export declare function getSharingState(): SharingState;
28
+ //# sourceMappingURL=state.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/sharing/state.svelte.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACzF,OAAO,EAAqB,KAAK,YAAY,EAAwB,MAAM,gCAAgC,CAAC;AAK5G,8EAA8E;AAC9E,qBAAa,YAAY;IAOZ,OAAO,CAAC,QAAQ,CAAC,WAAW;IANxC,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAS;gBAEI,WAAW,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC;IAErE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IACnC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAClC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAClC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAEpC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAKxC,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,aAAa;IAMf,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,SAAS,CAAC;IAMhF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO/D,OAAO,IAAI,IAAI;CAQf;AAED,sFAAsF;AACtF,wBAAgB,eAAe,IAAI,YAAY,CAO9C"}