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.
This commit is contained in:
Zarg 2025-02-02 03:13:22 +01:00
parent 81e0ce17cb
commit 416dab85cf
10 changed files with 285 additions and 3 deletions

View file

@ -4,5 +4,8 @@
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
<component name="WestSettings"><![CDATA[{}]]></component>
<component name="JavaScriptSettings">
<option name="languageLevel" value="FLOW" />
</component>
<component name="WestSettings">{}</component>
</project>

View file

@ -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

View file

@ -15,7 +15,10 @@ std::vector<std::wstring> g_webuiUrls = {
L"https://zaarrg.github.io/stremio-web-shell-fixes/",
L"https://web.stremio.com/"
};
std::vector<std::wstring> 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<bool> g_waitStarted(false);
// Extensions
std::map<std::wstring, std::wstring> g_extensionMap;
std::vector<std::wstring> g_scriptQueue;
// Updater
std::atomic_bool g_updaterRunning = false;

View file

@ -42,7 +42,10 @@ extern HANDLE g_hMutex;
extern HHOOK g_hMouseHook;
extern std::vector<std::wstring> g_webuiUrls;
extern std::vector<std::wstring> 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<bool> g_waitStarted;
// Extensions
extern std::map<std::wstring, std::wstring> g_extensionMap;
extern std::vector<std::wstring> g_scriptQueue;
// Updater
extern std::atomic_bool g_updaterRunning;

View file

@ -221,7 +221,11 @@ void HandleEvent(const std::string &ev, std::vector<std::string> &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="<<ev<<"\n";
}

39
src/utils/extensions.cpp Normal file
View file

@ -0,0 +1,39 @@
#include "extensions.h"
#include <iostream>
#include <string>
#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<std::wstring, std::wstring>& 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());
}
}

8
src/utils/extensions.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef EXTENSIONS_H
#define EXTENSIONS_H
#include <string>
void HandlePremidLogin(const std::wstring& finalUri);
#endif //EXTENSIONS_H

View file

@ -1,4 +1,6 @@
#include "helpers.h"
#include <iostream>
#include <tlhelp32.h>
#include <winhttp.h>
#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<std::string>()));
}
}
return true;
}
} catch (...) {
std::wcout << L"[HELPER]: Failed json parsing of domain whitelist for extensions..." << std::endl;
}
return false;
}

View file

@ -15,5 +15,7 @@ bool FileExists(const std::wstring& path);
bool DirectoryExists(const std::wstring& dirPath);
bool IsDuplicateProcessRunning(const std::vector<std::wstring>& targetProcesses);
bool isSubtitle(const std::wstring& filePath);
bool URLContainsAny(const std::wstring& url);
bool FetchAndParseWhitelist();
#endif // HELPERS_H

View file

@ -2,6 +2,7 @@
#include <string>
#include <thread>
#include <cmath>
#include <fstream>
#include <iostream>
#include <Shlwapi.h>
#include <wrl.h>
@ -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<ICoreWebView2NavigationStartingEventHandler>(
[](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();
}
}
}