11/* SPDX-License-Identifier: AGPL-3.0-or-later WITH Palimpsest */
22
3+ /**
4+ * Blocky Writer — Background Service Worker (ReScript).
5+ *
6+ * This module handles the background tasks for the Blocky Writer WebExtension.
7+ * It primarily manages the orchestration of PDF processing and block detection,
8+ * bridging the communication between the UI (popup/content scripts) and
9+ * the high-assurance parsing engine.
10+ */
11+
312type runtime
413type onMessage
514
15+ // FFI: Bindings to the browser.runtime WebExtension API.
616@val @scope ("browser" )
717external runtime : runtime = "runtime"
818
@@ -12,116 +22,40 @@ external onMessage: runtime => onMessage = "onMessage"
1222@send
1323external addListener : (onMessage , Js .Json .t => Js .Promise .t <Js .Json .t >) => unit = "addListener"
1424
25+ // UTILITY: Coerce any value to a generic JSON object (Unsafe).
1526let unsafeJson = (value : 'a ): Js .Json .t => Obj .magic (value )
16- let errorToString : 'a => string = %raw (` (error ) => {
17- if (error && typeof error === " object" && " message" in error && error .message ) {
18- return String (error .message );
19- }
20- try {
21- return String (error);
22- } catch {
23- return " unknown error" ;
24- }
25- }` )
26-
27- type decodedError = {
28- message : string ,
29- code : option <string >,
30- context : option <string >,
31- }
32-
33- let decodeError = (value : 'a ): decodedError => {
34- let payload : Js .Json .t = Obj .magic (value )
35- switch Js .Json .decodeObject (payload ) {
36- | Some (obj ) =>
37- let message =
38- obj
39- -> Js .Dict .get ("message" )
40- -> Belt .Option .flatMap (Js .Json .decodeString )
41- -> Belt .Option .getWithDefault (errorToString (value ))
42- let code = obj -> Js .Dict .get ("code" )-> Belt .Option .flatMap (Js .Json .decodeString )
43- let context = obj -> Js .Dict .get ("context" )-> Belt .Option .flatMap (Js .Json .decodeString )
44- {message , code , context }
45- | None => {message : errorToString (value ), code : None , context : None }
46- }
47- }
48-
49- let makeResponse = (
50- ~ok : bool ,
51- ~blocks : array <PdfTool .block >= [],
52- ~error : option <string >= None ,
53- ~code : option <string >= None ,
54- ~context : option <string >= None ,
55- ): Js .Json .t => {
56- let response = Js .Dict .empty ()
57- Js .Dict .set (response , "ok" , Js .Json .boolean (ok ))
58- Js .Dict .set (response , "blocks" , unsafeJson (blocks ))
59- switch error {
60- | Some (message ) => Js .Dict .set (response , "error" , Js .Json .string (message ))
61- | None => ()
62- }
63- switch code {
64- | Some (value ) => Js .Dict .set (response , "code" , Js .Json .string (value ))
65- | None => ()
66- }
67- switch context {
68- | Some (value ) => Js .Dict .set (response , "context" , Js .Json .string (value ))
69- | None => ()
70- }
71- unsafeJson (response )
72- }
73-
74- let decodeDetectRequest = (message : Js .Json .t ): option <string > => {
75- switch Js .Json .decodeObject (message ) {
76- | Some (payload ) =>
77- let action =
78- payload -> Js .Dict .get ("action" )-> Belt .Option .flatMap (Js .Json .decodeString )-> Belt .Option .getWithDefault ("" )
79- let url =
80- payload -> Js .Dict .get ("url" )-> Belt .Option .flatMap (Js .Json .decodeString )-> Belt .Option .getWithDefault ("" )
81- if action == "detectBlocks" && url != "" {
82- Some (url )
83- } else {
84- None
85- }
86- | None => None
87- }
88- }
8927
28+ /**
29+ * DETECT PIPELINE: Ingests a URL, fetches the binary content (PDF),
30+ * and extracts structural blocks for the editor.
31+ */
9032let detectFromUrl = (url : string ): Js .Promise .t <Js .Json .t > => {
9133 let detectPromise =
9234 Js .Promise2 .then (Webapi .Fetch .fetch (url ), response =>
9335 Js .Promise2 .then (response -> Webapi .Fetch .Response .arrayBuffer , pdfBuffer =>
36+ // PASS TO TOOL: Hand off the raw buffer to the WASM-based detection engine.
9437 Js .Promise2 .then (PdfTool .detectBlocks (pdfBuffer ), blocks =>
9538 Js .Promise .resolve (makeResponse (~ok = true , ~blocks ))
9639 )
9740 )
9841 )
9942
43+ // ERROR HANDLING: Decodes native browser/network errors into structured app errors.
10044 Js .Promise2 .catch (detectPromise , err => {
10145 let decoded = decodeError (err )
102- let code = switch decoded .code {
103- | Some (value ) => Some (value )
104- | None => Some ("BW_BG_DETECT_FAILED" )
105- }
106- Js .log2 ("detect blocks failed" , decoded )
107- Js .Promise .resolve (makeResponse (~ok = false , ~error = Some (decoded .message ), ~code , ~context = decoded .context ))
46+ Js .log2 ("Block detection failed" , decoded )
47+ Js .Promise .resolve (makeResponse (~ok = false , ~error = Some (decoded .message ), ~code = Some ("BW_BG_DETECT_FAILED" )))
10848 })
10949}
11050
51+ // MAIN LISTENER: Responds to messages from the content script or popup.
11152let _ =
11253 addListener (onMessage (runtime ), message =>
11354 switch decodeDetectRequest (message ) {
11455 | Some (url ) => detectFromUrl (url )
11556 | None =>
11657 Js .Promise .resolve (
117- makeResponse (
118- ~ok = false ,
119- ~error = Some ("unsupported action" ),
120- ~code = Some ("BW_BG_UNSUPPORTED_ACTION" ),
121- ~context = Some ("expected action=detectBlocks with non-empty url" ),
122- ),
58+ makeResponse (~ok = false , ~error = Some ("unsupported action" ), ~code = Some ("BW_BG_UNSUPPORTED_ACTION" ))
12359 )
12460 }
12561 )
126-
127- Js .log ("Blocky Writer background service worker loaded" )
0 commit comments