From 416dab85cf13425684f91ebe299deb5a60971988 Mon Sep 17 00:00:00 2001
From: Zarg <62082797+Zaarrg@users.noreply.github.com>
Date: Sun, 2 Feb 2025 03:13:22 +0100
Subject: [PATCH] Added advanced Browser Extensions Support
- Added extension domain whitelist gotten from extensions.json on github for allowed domains to navigate for browser extension usage. App allow these domain to be navigated to other are open externally.
- Added script Queue to run scripts on navigation complete
- Added "Home" navigate event arg, to navigate back to home page
- Added Back to Stremio button when not on Stremio page
- Partial permid support with custom patch. Still some features not working.
---
.idea/misc.xml | 5 +-
CMakeLists.txt | 3 +
src/core/globals.cpp | 4 ++
src/core/globals.h | 4 ++
src/ui/mainwindow.cpp | 6 +-
src/utils/extensions.cpp | 39 +++++++++++++
src/utils/extensions.h | 8 +++
src/utils/helpers.cpp | 95 ++++++++++++++++++++++++++++++
src/utils/helpers.h | 2 +
src/webview/webview.cpp | 122 ++++++++++++++++++++++++++++++++++++++-
10 files changed, 285 insertions(+), 3 deletions(-)
create mode 100644 src/utils/extensions.cpp
create mode 100644 src/utils/extensions.h
diff --git a/.idea/misc.xml b/.idea/misc.xml
index e479d63..73ba8b2 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,5 +4,8 @@
-
+
+
+
+ {}
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3e69274..6d1cdde 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,6 +52,8 @@ set(SOURCES
src/ui/mainwindow.h
src/ui/mainwindow.cpp
src/resource.h
+ src/utils/extensions.cpp
+ src/utils/extensions.h
)
add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
@@ -63,6 +65,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
Shcore.lib
Msimg32.lib
winhttp.lib
+ shlwapi.lib
nlohmann_json::nlohmann_json
unofficial::webview2::webview2
OpenSSL::SSL
diff --git a/src/core/globals.cpp b/src/core/globals.cpp
index 93eabbf..8ae4999 100644
--- a/src/core/globals.cpp
+++ b/src/core/globals.cpp
@@ -15,7 +15,10 @@ std::vector g_webuiUrls = {
L"https://zaarrg.github.io/stremio-web-shell-fixes/",
L"https://web.stremio.com/"
};
+std::vector g_domainWhitelist;
std::string g_updateUrl= "https://raw.githubusercontent.com/Zaarrg/stremio-desktop-v5/refs/heads/webview-windows/version/version.json";
+std::wstring g_extensionsDetailsUrl= L"https://raw.githubusercontent.com/Zaarrg/stremio-desktop-v5/refs/heads/webview-windows/extensions/extensions.json";
+std::wstring g_webuiUrl;
// Command-line args
bool g_streamingServer = true;
@@ -85,6 +88,7 @@ std::atomic g_waitStarted(false);
// Extensions
std::map g_extensionMap;
+std::vector g_scriptQueue;
// Updater
std::atomic_bool g_updaterRunning = false;
diff --git a/src/core/globals.h b/src/core/globals.h
index cd7ea81..d75fc14 100644
--- a/src/core/globals.h
+++ b/src/core/globals.h
@@ -42,7 +42,10 @@ extern HANDLE g_hMutex;
extern HHOOK g_hMouseHook;
extern std::vector g_webuiUrls;
+extern std::vector g_domainWhitelist;
extern std::string g_updateUrl;
+extern std::wstring g_extensionsDetailsUrl;
+extern std::wstring g_webuiUrl;
// Args
extern bool g_streamingServer;
@@ -131,6 +134,7 @@ extern std::atomic g_waitStarted;
// Extensions
extern std::map g_extensionMap;
+extern std::vector g_scriptQueue;
// Updater
extern std::atomic_bool g_updaterRunning;
diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp
index d02943a..612430a 100644
--- a/src/ui/mainwindow.cpp
+++ b/src/ui/mainwindow.cpp
@@ -221,7 +221,11 @@ void HandleEvent(const std::string &ev, std::vector &args)
ShellExecuteW(nullptr, L"open", uri.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
} else if (ev=="navigate") {
std::wstring uri(args[0].begin(), args[0].end());
- g_webview->Navigate(uri.c_str());
+ if (args[0] == "home") {
+ g_webview->Navigate(g_webuiUrl.c_str());
+ } else {
+ g_webview->Navigate(uri.c_str());
+ }
} else {
std::cout<<"Unknown event="<
+#include
+#include "../core/globals.h"
+
+void HandlePremidLogin(const std::wstring& finalUri) {
+ if (finalUri.rfind(L"https://login.premid.app", 0) == 0 && finalUri.rfind(L"https://discord.com", 0) != 0) {
+ std::wstring extensionId;
+ auto it = std::find_if(g_extensionMap.begin(), g_extensionMap.end(),
+ [](const std::pair& p) -> bool {
+ return p.first.find(L"premid") != std::wstring::npos;
+ });
+ if (it != g_extensionMap.end()) {
+ extensionId = it->second;
+ } else {
+ std::wcout << L"[EXTENSIONS]: Extension id not found\n";
+ g_webview->Navigate(g_webuiUrl.c_str());
+ return;
+ }
+
+ std::wstring codeParam;
+ size_t codePos = finalUri.find(L"code=");
+ if (codePos != std::wstring::npos) {
+ codePos += 5; // Move past "code="
+ size_t ampPos = finalUri.find(L'&', codePos);
+ if (ampPos == std::wstring::npos) {
+ codeParam = finalUri.substr(codePos);
+ } else {
+ codeParam = finalUri.substr(codePos, ampPos - codePos);
+ }
+ }
+ std::wstring script = L"globalThis.getAuthorizationCode(\"" + codeParam + L"\");";
+ g_scriptQueue.push_back(script);
+
+ std::wstring uri = L"chrome-extension://" + extensionId + L"/popup.html";
+ g_webview->Navigate(uri.c_str());
+ }
+}
diff --git a/src/utils/extensions.h b/src/utils/extensions.h
new file mode 100644
index 0000000..bfcad1f
--- /dev/null
+++ b/src/utils/extensions.h
@@ -0,0 +1,8 @@
+#ifndef EXTENSIONS_H
+#define EXTENSIONS_H
+
+#include
+
+void HandlePremidLogin(const std::wstring& finalUri);
+
+#endif //EXTENSIONS_H
diff --git a/src/utils/helpers.cpp b/src/utils/helpers.cpp
index f7d0936..49a808e 100644
--- a/src/utils/helpers.cpp
+++ b/src/utils/helpers.cpp
@@ -1,4 +1,6 @@
#include "helpers.h"
+
+#include
#include
#include
#include "../core/globals.h"
@@ -189,4 +191,97 @@ std::wstring GetFirstReachableUrl() {
}
// Fallback to first URL or handle error
return g_webuiUrls.empty() ? L"" : g_webuiUrls[0];
+}
+
+bool URLContainsAny(const std::wstring& url) {
+ if (std::find(g_domainWhitelist.begin(), g_domainWhitelist.end(), g_webuiUrl) == g_domainWhitelist.end()) {
+ g_domainWhitelist.push_back(g_webuiUrl);
+ }
+ return std::any_of(g_domainWhitelist.begin(), g_domainWhitelist.end(), [&](const std::wstring& sub) {
+ return url.find(sub) != std::wstring::npos;
+ });
+}
+
+bool FetchAndParseWhitelist() {
+ HINTERNET hSession = WinHttpOpen(L"DomainWhitelist Updater",
+ WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
+ if (!hSession) return false;
+
+ WinHttpSetTimeouts(hSession, 3000, 3000, 3000, 3000);
+
+ URL_COMPONENTS urlComp = { sizeof(URL_COMPONENTS) };
+ urlComp.dwHostNameLength = (DWORD)-1;
+ urlComp.dwUrlPathLength = (DWORD)-1;
+ urlComp.dwSchemeLength = (DWORD)-1;
+
+ if (!WinHttpCrackUrl(g_extensionsDetailsUrl.c_str(), (DWORD)g_extensionsDetailsUrl.length(), 0, &urlComp)) {
+ WinHttpCloseHandle(hSession);
+ return false;
+ }
+
+ std::wstring host(urlComp.lpszHostName, urlComp.dwHostNameLength);
+ std::wstring path(urlComp.lpszUrlPath, urlComp.dwUrlPathLength);
+ INTERNET_PORT port = urlComp.nPort;
+ bool useSSL = (urlComp.nScheme == INTERNET_SCHEME_HTTPS);
+
+ HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), port, 0);
+ if (!hConnect) {
+ WinHttpCloseHandle(hSession);
+ return false;
+ }
+
+ HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path.c_str(),
+ NULL, NULL, NULL, useSSL ? WINHTTP_FLAG_SECURE : 0);
+ if (!hRequest) {
+ WinHttpCloseHandle(hConnect);
+ WinHttpCloseHandle(hSession);
+ return false;
+ }
+
+ if (!WinHttpSendRequest(hRequest, NULL, 0, NULL, 0, 0, 0)) {
+ WinHttpCloseHandle(hRequest);
+ WinHttpCloseHandle(hConnect);
+ WinHttpCloseHandle(hSession);
+ return false;
+ }
+
+ if (!WinHttpReceiveResponse(hRequest, NULL)) {
+ WinHttpCloseHandle(hRequest);
+ WinHttpCloseHandle(hConnect);
+ WinHttpCloseHandle(hSession);
+ return false;
+ }
+
+ std::string response;
+ DWORD bytesRead = 0;
+ do {
+ char buffer[4096];
+ if (!WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead)) {
+ break;
+ }
+ if (bytesRead > 0) {
+ response.append(buffer, bytesRead);
+ }
+ } while (bytesRead > 0);
+
+ WinHttpCloseHandle(hRequest);
+ WinHttpCloseHandle(hConnect);
+ WinHttpCloseHandle(hSession);
+
+ try {
+ json j = json::parse(response);
+ if (j.contains("domains") && j["domains"].is_array()) {
+ g_domainWhitelist.clear();
+ for (const auto& domain : j["domains"]) {
+ if (domain.is_string()) {
+ g_domainWhitelist.push_back(Utf8ToWstring(domain.get()));
+ }
+ }
+ return true;
+ }
+ } catch (...) {
+ std::wcout << L"[HELPER]: Failed json parsing of domain whitelist for extensions..." << std::endl;
+ }
+
+ return false;
}
\ No newline at end of file
diff --git a/src/utils/helpers.h b/src/utils/helpers.h
index 8655309..53e9415 100644
--- a/src/utils/helpers.h
+++ b/src/utils/helpers.h
@@ -15,5 +15,7 @@ bool FileExists(const std::wstring& path);
bool DirectoryExists(const std::wstring& dirPath);
bool IsDuplicateProcessRunning(const std::vector& targetProcesses);
bool isSubtitle(const std::wstring& filePath);
+bool URLContainsAny(const std::wstring& url);
+bool FetchAndParseWhitelist();
#endif // HELPERS_H
diff --git a/src/webview/webview.cpp b/src/webview/webview.cpp
index 96edf74..d4c3713 100644
--- a/src/webview/webview.cpp
+++ b/src/webview/webview.cpp
@@ -2,6 +2,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -9,6 +10,7 @@
#include "../utils/crashlog.h"
#include "../utils/helpers.h"
#include "../ui/mainwindow.h"
+#include "../utils/extensions.h"
static const wchar_t* EXEC_SHELL_SCRIPT = LR"JS_CODE(
try {
@@ -86,6 +88,78 @@ static const wchar_t* INJECTED_KEYDOWN_SCRIPT = LR"JS(
})();
)JS";
+static const wchar_t* INJECTED_BUTTON_SCRIPT = LR"JS(
+(function() {
+ // Create the button element
+ var btn = document.createElement('button');
+ btn.id = 'goBackStremioBtn';
+
+ // Style the button:
+ btn.style.position = 'fixed';
+ btn.style.bottom = '15px';
+ btn.style.right = '15px';
+ btn.style.zIndex = '9999';
+ btn.style.backgroundColor = '#121024'; // Purple
+ btn.style.color = 'white';
+ btn.style.border = 'none';
+ btn.style.borderRadius = '30px';
+ btn.style.padding = '12px 20px';
+ btn.style.fontSize = '16px';
+ btn.style.fontWeight = 'bold';
+ btn.style.display = 'flex';
+ btn.style.alignItems = 'center';
+ btn.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
+ btn.style.cursor = 'pointer';
+ btn.style.transition = 'background-color 0.3s ease';
+
+ // Hover effect:
+ btn.addEventListener('mouseenter', function() {
+ btn.style.backgroundColor = '#211e39'; // Gray
+ });
+ btn.addEventListener('mouseleave', function() {
+ btn.style.backgroundColor = '#121024';
+ });
+
+ // Create an image element for the logo
+ var img = document.createElement('img');
+ img.src = 'https://stremio.zarg.me/images/stremio_symbol.png';
+ img.alt = 'Logo';
+ img.style.height = '24px';
+ img.style.width = '24px';
+ img.style.marginRight = '8px';
+
+img.addEventListener('error', function() {
+ img.style.display = 'none';
+});
+
+ // Create a text element
+ var txt = document.createElement('span');
+ txt.textContent = 'Back to Stremio';
+
+ // Append the logo and text to the button
+ btn.appendChild(img);
+ btn.appendChild(txt);
+
+ // On click go home
+ btn.addEventListener('click', function() {
+ const payload = {
+ type: 6,
+ object: "transport",
+ method: "handleInboundJSON",
+ id: 666,
+ args: [
+ "navigate",
+ [ "home" ]
+ ]
+ };
+ window.chrome.webview.postMessage(JSON.stringify(payload));
+ });
+
+ // Append the button to the document body
+ document.body.appendChild(btn);
+})();
+)JS";
+
void WaitAndRefreshIfNeeded()
{
std::thread([](){
@@ -222,7 +296,9 @@ void InitWebView2(HWND hWnd)
std::wcout << L"[WEBVIEW]: Checking web ui endpoints..." << std::endl;
std::wstring foundUrl = GetFirstReachableUrl();
std::wstring* pResult = new std::wstring(foundUrl);
+ g_webuiUrl = foundUrl;
PostMessage(g_hWnd, WM_REACHABILITY_DONE, (WPARAM)pResult, 0);
+ FetchAndParseWhitelist();
}).detach();
return S_OK;
}).Get()
@@ -250,14 +326,34 @@ static void SetupWebMessageHandler()
{
BOOL isSuccess;
args->get_IsSuccess(&isSuccess);
+
+ // Retrieve the final URL
+ wil::unique_cotaskmem_string rawUri;
+ sender->get_Source(&rawUri);
+ std::wstring finalUri = rawUri ? rawUri.get() : L"";
+ std::wcout << L"[WEBVIEW]: Navigation try to " << finalUri << std::endl;
+
+ // Add back to stremio button if not on stremio
+ if (finalUri.find(g_webuiUrl) == std::wstring::npos) {
+ sender->ExecuteScript(INJECTED_BUTTON_SCRIPT, nullptr);
+ }
+
if(isSuccess) {
std::cout<<"[WEBVIEW]: Navigation Complete - Success\n";
sender->ExecuteScript(EXEC_SHELL_SCRIPT, nullptr);
+ // Flush the script queue.
+ if (!g_scriptQueue.empty()) {
+ for (const auto &script : g_scriptQueue) {
+ sender->ExecuteScript(script.c_str(), nullptr);
+ }
+ g_scriptQueue.clear();
+ }
} else {
std::cout<<"[WEBVIEW]: Navigation failed\n";
if(g_hSplash && !g_waitStarted.exchange(true)) {
WaitAndRefreshIfNeeded();
}
+ HandlePremidLogin(finalUri);
}
return S_OK;
}).Get(),
@@ -422,6 +518,10 @@ static void SetupWebMessageHandler()
SendToJS("FileDropped", j);
return S_OK;
}
+ if (URLContainsAny(wuri)) {
+ g_webview->Navigate(wuri.c_str());
+ return S_OK;
+ }
// For non-file URIs, open externally
ShellExecuteW(nullptr, L"open", uri.get(), nullptr, nullptr, SW_SHOWNORMAL);
}
@@ -431,6 +531,26 @@ static void SetupWebMessageHandler()
&newWindowToken
);
+ // For redirects like window.location.replace
+ EventRegistrationToken navstartedToken;
+ g_webview->add_NavigationStarting(
+ Microsoft::WRL::Callback(
+ [](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
+ {
+ wil::unique_cotaskmem_string uri;
+ args->get_Uri(&uri);
+ std::wstring destination(uri.get());
+
+ if (!URLContainsAny(destination)) {
+ args->put_Cancel(TRUE);
+ ShellExecuteW(nullptr, L"open", uri.get(), nullptr, nullptr, SW_SHOWNORMAL);
+ }
+ return S_OK;
+ }
+ ).Get(),
+ &navstartedToken
+ );
+
// FullScreen
EventRegistrationToken cfeToken;
g_webview->add_ContainsFullScreenElementChanged(
@@ -526,4 +646,4 @@ void refreshWeb(const bool refreshAll) {
if (g_webview) {
g_webview->Reload();
}
-}
+}
\ No newline at end of file