From 14ea7b964f65460c08f39d42e1621aabd1db22fc Mon Sep 17 00:00:00 2001
From: AAGaming <aagaming@riseup.net>
Date: Sat, 4 May 2024 22:39:30 -0400
Subject: implement fetch and external resource request apis

---
 frontend/src/plugin-loader.tsx | 62 +++++++++++++++++++++++++++++++++---------
 1 file changed, 49 insertions(+), 13 deletions(-)

(limited to 'frontend/src/plugin-loader.tsx')

diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx
index 75b09091..bb6fa6dd 100644
--- a/frontend/src/plugin-loader.tsx
+++ b/frontend/src/plugin-loader.tsx
@@ -42,7 +42,7 @@ const FilePicker = lazy(() => import('./components/modals/filepicker'));
 
 declare global {
   interface Window {
-    __DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyPluginBackendAPIInit?: {
+    __DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyLoaderAPIInit?: {
       connect: (version: number, key: string) => any; // Returns the backend API used above, no real point adding types to this.
     };
   }
@@ -51,6 +51,10 @@ declare global {
 /** Map of event names to event listeners */
 type listenerMap = Map<string, Set<(...args: any) => any>>;
 
+interface DeckyRequestInit extends RequestInit {
+  excludedHeaders: string[];
+}
+
 const callPluginMethod = DeckyBackend.callable<[pluginName: string, method: string, ...args: any], any>(
   'loader/call_plugin_method',
 );
@@ -357,7 +361,7 @@ class PluginLoader extends Logger {
           let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`, {
             credentials: 'include',
             headers: {
-              Authentication: deckyAuthToken,
+              'X-Decky-Auth': deckyAuthToken,
             },
           });
           if (res.ok) {
@@ -484,12 +488,31 @@ class PluginLoader extends Logger {
     });
   }
 
-  /* TODO replace with the following flow (or similar) so we can reuse the JS Fetch API
-        frontend --request URL only--> backend (ws method)
-        backend --new temporary backend URL--> frontend (ws response)
-        frontend <--> backend <--> target URL (over http!)
-      */
-  async fetchNoCors(url: string, request: any = {}) {
+  // Useful for audio/video streams
+  getExternalResourceURL(url: string) {
+    return `http://127.0.0.1:1337/fetch?auth=${deckyAuthToken}&fetch_url=${encodeURIComponent(url)}`;
+  }
+
+  // Same syntax as fetch but only supports the url-based syntax and an object for headers since it's the most common usage pattern
+  fetch(input: string, init?: DeckyRequestInit | undefined): Promise<Response> {
+    const headers: { [name: string]: string } = {
+      ...(init?.headers as { [name: string]: string }),
+      'X-Decky-Auth': deckyAuthToken,
+      'X-Decky-Fetch-URL': input,
+    };
+
+    if (init?.excludedHeaders) {
+      headers['X-Decky-Fetch-Excluded-Headers'] = init.excludedHeaders.join(', ');
+    }
+
+    return fetch('http://127.0.0.1:1337/fetch', {
+      ...init,
+      credentials: 'include',
+      headers,
+    });
+  }
+
+  async legacyFetchNoCors(url: string, request: any = {}) {
     let method: string;
     const req = { headers: {}, ...request, data: request.body };
     req?.body && delete req.body;
@@ -513,10 +536,10 @@ class PluginLoader extends Logger {
 
   initPluginBackendAPI() {
     // Things will break *very* badly if plugin code touches this outside of @decky/backend, so lets make that clear.
-    window.__DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyPluginBackendAPIInit = {
+    window.__DECKY_SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_deckyLoaderAPIInit = {
       connect: (version: number, pluginName: string) => {
-        if (version <= 0) {
-          throw new Error(`Plugin ${pluginName} requested invalid backend api version ${version}.`);
+        if (version < 1 || version > 1) {
+          throw new Error(`Plugin ${pluginName} requested unsupported backend api version ${version}.`);
         }
 
         const eventListeners: listenerMap = new Map();
@@ -543,9 +566,22 @@ class PluginLoader extends Logger {
               set?.delete(listener);
             }
           },
+          openFilePicker: this.openFilePicker.bind(this),
+          executeInTab: DeckyBackend.callable<
+            [tab: String, runAsync: Boolean, code: string],
+            { success: boolean; result: any }
+          >('utilities/execute_in_tab'),
+          fetch: this.fetch.bind(this),
+          getExternalResourceURL: this.getExternalResourceURL.bind(this),
+          injectCssIntoTab: DeckyBackend.callable<[tab: string, style: string], string>(
+            'utilities/inject_css_into_tab',
+          ),
+          removeCssFromTab: DeckyBackend.callable<[tab: string, cssId: string]>('utilities/remove_css_from_tab'),
+          routerHook: this.routerHook,
+          toaster: this.toaster,
         };
 
-        this.debug(`${pluginName} connected to backend API.`);
+        this.debug(`${pluginName} connected to loader API.`);
         return backendAPI;
       },
     };
@@ -591,7 +627,7 @@ class PluginLoader extends Logger {
           args,
         );
       },
-      fetchNoCors: this.fetchNoCors,
+      fetchNoCors: this.legacyFetchNoCors,
       executeInTab: DeckyBackend.callable<
         [tab: String, runAsync: Boolean, code: string],
         { success: boolean; result: any }
-- 
cgit v1.2.3

