pachet commited on
Commit
be6dab9
·
1 Parent(s): 437c6c0

Fallback MIDI export in embedded Spaces

Browse files
Files changed (3) hide show
  1. README.md +9 -4
  2. frontend/app.js +32 -4
  3. frontend/index.html +3 -3
README.md CHANGED
@@ -96,9 +96,12 @@ intentionally remembers a file, not a folder, because Chrome can reject folder
96
  access for protected locations.
97
 
98
  If the browser does not support the file-save picker, the export falls back to a
99
- normal browser download. The backend also keeps `POST /api/sessions/{session_id}/save-midi`
100
- as a server-side fallback route, but browser users should prefer the ZIP
101
- download path because it writes to a location they choose locally.
 
 
 
102
 
103
  ## High-Level Architecture
104
 
@@ -369,7 +372,9 @@ CONTINUATOR_DB_PATH=/data/continuator.sqlite3
369
  ```
370
 
371
  The browser ZIP export does not require persistent Space storage because the
372
- archive is downloaded to the user. The server-side fallback `save-midi` route
 
 
373
  writes inside the container unless `CONTINUATOR_SESSION_EXPORT_DIR` points to a
374
  mounted volume, so it is mainly useful for local development or managed storage
375
  setups.
 
96
  access for protected locations.
97
 
98
  If the browser does not support the file-save picker, the export falls back to a
99
+ normal browser download. Hugging Face embeds Spaces in a cross-origin frame, so
100
+ the embedded Space also uses the normal browser download flow; open the direct
101
+ Space URL if you want Chrome's file-save picker and `Don't ask again` behavior.
102
+ The backend also keeps `POST /api/sessions/{session_id}/save-midi` as a
103
+ server-side fallback route, but browser users should prefer the ZIP download
104
+ path because it writes to a location they choose locally.
105
 
106
  ## High-Level Architecture
107
 
 
372
  ```
373
 
374
  The browser ZIP export does not require persistent Space storage because the
375
+ archive is downloaded to the user. In the embedded Hugging Face Space page,
376
+ browser security rules block Chrome's file-save picker, so exports fall back to
377
+ the browser's normal download flow. The server-side fallback `save-midi` route
378
  writes inside the container unless `CONTINUATOR_SESSION_EXPORT_DIR` points to a
379
  mounted volume, so it is mainly useful for local development or managed storage
380
  setups.
frontend/app.js CHANGED
@@ -544,8 +544,27 @@ function safeLocalStorageSet(key, value) {
544
  }
545
  }
546
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
  function midiExportZipSaveSupported() {
548
- return typeof window.showSaveFilePicker === "function";
 
 
 
549
  }
550
 
551
  function midiExportZipRememberSupported() {
@@ -4734,14 +4753,23 @@ async function saveSessionMidi() {
4734
  setPhraseMessage("MIDI export cancelled.");
4735
  return;
4736
  }
4737
- if (error?.name === "NotAllowedError" || /system files/i.test(error?.message || "")) {
 
 
 
 
 
 
4738
  throw new Error(
4739
  "Chrome blocked that location. Save the ZIP in Downloads, Documents, Desktop, or Music instead of a system, home, or project folder.",
4740
  );
 
 
4741
  }
4742
- throw error;
4743
  }
4744
- destinationLabel = await writeZipBlobToFileHandle(payload.blob, fileHandle);
 
 
4745
  } else {
4746
  destinationLabel = `${downloadZipBlob(payload.blob, payload.archive_file_name)} in your browser downloads`;
4747
  }
 
544
  }
545
  }
546
 
547
+ function isCrossOriginSubframe() {
548
+ if (window.self === window.top) {
549
+ return false;
550
+ }
551
+ try {
552
+ void window.top.location.href;
553
+ return false;
554
+ } catch {
555
+ return true;
556
+ }
557
+ }
558
+
559
+ function filePickerBlockedByFrame(error) {
560
+ return /cross.?origin|sub.?frames?|file picker/i.test(error?.message || "");
561
+ }
562
+
563
  function midiExportZipSaveSupported() {
564
+ return (
565
+ typeof window.showSaveFilePicker === "function" &&
566
+ !isCrossOriginSubframe()
567
+ );
568
  }
569
 
570
  function midiExportZipRememberSupported() {
 
4753
  setPhraseMessage("MIDI export cancelled.");
4754
  return;
4755
  }
4756
+ if (filePickerBlockedByFrame(error)) {
4757
+ destinationLabel = `${downloadZipBlob(payload.blob, payload.archive_file_name)} in your browser downloads`;
4758
+ setSessionMidiSaveResult(
4759
+ "File picker is blocked in this embedded page; using browser downloads instead.",
4760
+ );
4761
+ fileHandle = null;
4762
+ } else if (error?.name === "NotAllowedError" || /system files/i.test(error?.message || "")) {
4763
  throw new Error(
4764
  "Chrome blocked that location. Save the ZIP in Downloads, Documents, Desktop, or Music instead of a system, home, or project folder.",
4765
  );
4766
+ } else {
4767
+ throw error;
4768
  }
 
4769
  }
4770
+ if (fileHandle) {
4771
+ destinationLabel = await writeZipBlobToFileHandle(payload.blob, fileHandle);
4772
+ }
4773
  } else {
4774
  destinationLabel = `${downloadZipBlob(payload.blob, payload.archive_file_name)} in your browser downloads`;
4775
  }
frontend/index.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>Web Continuator</title>
7
- <link rel="stylesheet" href="/assets/styles.css?v=20260602-session-midi-zip2" />
8
  </head>
9
  <body>
10
  <div class="page-aura"></div>
@@ -167,7 +167,7 @@
167
  <p>
168
  In Chrome and Edge, <strong>Don't ask again</strong> remembers the
169
  selected ZIP file and overwrites it on future saves. Other browsers
170
- use the normal download flow.
171
  </p>
172
  </section>
173
 
@@ -1159,6 +1159,6 @@
1159
  </section>
1160
  </main>
1161
 
1162
- <script type="module" src="/assets/app.js?v=20260602-session-midi-zip2"></script>
1163
  </body>
1164
  </html>
 
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>Web Continuator</title>
7
+ <link rel="stylesheet" href="/assets/styles.css?v=20260602-session-midi-zip3" />
8
  </head>
9
  <body>
10
  <div class="page-aura"></div>
 
167
  <p>
168
  In Chrome and Edge, <strong>Don't ask again</strong> remembers the
169
  selected ZIP file and overwrites it on future saves. Other browsers
170
+ and embedded Hugging Face pages use the normal download flow.
171
  </p>
172
  </section>
173
 
 
1159
  </section>
1160
  </main>
1161
 
1162
+ <script type="module" src="/assets/app.js?v=20260602-session-midi-zip3"></script>
1163
  </body>
1164
  </html>