diff --git a/api/service-worker/init.js b/api/service-worker/init.js index e36b37c9e..faac3fd42 100644 --- a/api/service-worker/init.js +++ b/api/service-worker/init.js @@ -198,6 +198,7 @@ export async function onActivate (event) { export async function onFetch (event) { const info = new ServiceWorkerInfo(event.detail) const exists = workers.has(info.hash) + console.log(exists, event.detail) // this may be an early fetch, just try waiting at most // 32*16 milliseconds for the worker to be available or @@ -377,7 +378,7 @@ hooks.onReady(async () => { const result = await ipc.request('serviceWorker.getRegistrations') if (Array.isArray(result.data)) { for (const info of result.data) { - await navigator.serviceWorker.register(info.scriptURL, info) + //await navigator.serviceWorker.register(info.scriptURL, info) } } }) diff --git a/api/service-worker/worker.js b/api/service-worker/worker.js index 2bd670a17..8808d9bda 100644 --- a/api/service-worker/worker.js +++ b/api/service-worker/worker.js @@ -410,6 +410,7 @@ export async function onMessage (event) { } const url = new URL(data.fetch.request.url) + console.log('FetchEvent: %s', url.href) const fetchEvent = new FetchEvent('fetch', { clientId: data.fetch.client.id, fetchId: data.fetch.request.id, diff --git a/src/runtime/bridge/bridge.cc b/src/runtime/bridge/bridge.cc index 2078f55ba..3a1538d0c 100644 --- a/src/runtime/bridge/bridge.cc +++ b/src/runtime/bridge/bridge.cc @@ -454,6 +454,14 @@ export * from '{{url}}' if (request->hostname.size() > 0) { auto origin = webview::Origin(request->scheme + "://" + request->hostname); auto serviceWorker = app->runtime.serviceWorkerManager.get(origin.name()); + + debug(">> %d %d %d %d", + serviceWorker != nullptr, + request->hostname != globalBundleIdentifier, + window->options.shouldPreferServiceWorker, + serviceWorker->container.registrations.size() > 0 + ); + if ( serviceWorker != nullptr && request->hostname != globalBundleIdentifier && @@ -463,6 +471,7 @@ export * from '{{url}}' auto fetch = serviceworker::Request(); fetch.method = request->method; fetch.scheme = request->scheme; + fetch.url.scheme = request->scheme; fetch.url.hostname = request->hostname; fetch.url.pathname = request->pathname; fetch.url.searchParams.set(request->query); @@ -477,6 +486,7 @@ export * from '{{url}}' const auto app = App::sharedApplication(); const auto options = serviceworker::Fetch::Options { request->client }; + debug("FETCH PLZ: %s", fetch.str().c_str()); const auto fetched = serviceWorker->fetch(fetch, options, [ this, applicationResources, @@ -541,6 +551,7 @@ export * from '{{url}}' contentLocation = resourcePath.substr(applicationResources.size(), resourcePath.size()); } + debug("RESOURCE PATH: %s", resourcePath.c_str()); auto resource = filesystem::Resource(resourcePath); if (!resource.exists()) { @@ -607,7 +618,10 @@ export * from '{{url}}' } } - if (request->hostname == userConfig["meta_bundle_identifier"]) { + if ( + request->hostname == userConfig["meta_bundle_identifier"] || + request->hostname == globalConfig["meta_bundle_identifier"] + ) { const auto resolved = this->navigator.location.resolve(request->pathname, applicationResources); if (resolved.redirect) { @@ -786,7 +800,6 @@ export * from '{{url}}' url.hostname = toLowerCase(bundleIdentifier); url.pathname = contentLocation; url.search = request->query; - debug("URL1: %s", url.str().c_str()); const auto moduleImportProxy = tmpl( String(reinterpret_cast(resource.read())).find("export default") != String::npos @@ -909,7 +922,6 @@ export * from '{{url}}' url.hostname = toLowerCase(bundleIdentifier); url.pathname = "/socket" + pathname; url.search = request->query; - debug("URL2: %s", url.str().c_str()); const auto moduleImportProxy = tmpl( String(reinterpret_cast(resource.read())).find("export default") != String::npos ? ESM_IMPORT_PROXY_TEMPLATE_WITH_DEFAULT_EXPORT @@ -994,92 +1006,92 @@ export * from '{{url}}' const auto& scheme = entry.first; const auto id = rand64(); - if (globalUserConfig["meta_bundle_identifier"] != this->userConfig["meta_bundle_identifier"]) { - if (globalProtocolHandlers.contains(scheme)) { - continue; - } - } - - auto scriptURL = trim(entry.second); + if ( + globalUserConfig["meta_bundle_identifier"] == this->userConfig["meta_bundle_identifier"] || + !globalProtocolHandlers.contains(scheme) + ) { - if (scriptURL.size() == 0) { - continue; - } + auto scriptURL = trim(entry.second); - if (!scriptURL.starts_with(".") && !scriptURL.starts_with("/")) { - continue; - } + if (scriptURL.size() == 0) { + continue; + } - if (scriptURL.starts_with(".")) { - scriptURL = scriptURL.substr(1, scriptURL.size()); - } + if (!scriptURL.starts_with(".") && !scriptURL.starts_with("/")) { + continue; + } - String scope = "/"; + if (scriptURL.starts_with(".")) { + scriptURL = scriptURL.substr(1, scriptURL.size()); + } - auto scopeParts = split(scriptURL, "/"); - if (scopeParts.size() > 0) { - scopeParts = Vector(scopeParts.begin(), scopeParts.end() - 1); - scope = join(scopeParts, "/"); - } + String scope = "/"; - scriptURL = ( - #if SOCKET_RUNTIME_PLATFORM_ANDROID - "https://" + - #else - "socket://" + - #endif - this->userConfig["meta_bundle_identifier"] + - scriptURL - ); + auto scopeParts = split(scriptURL, "/"); + if (scopeParts.size() > 0) { + scopeParts = Vector(scopeParts.begin(), scopeParts.end() - 1); + scope = join(scopeParts, "/"); + } - debug("scriptURL: %s", scriptURL.c_str()); + scriptURL = ( + #if SOCKET_RUNTIME_PLATFORM_ANDROID + "https://" + + #else + "socket://" + + #endif + this->userConfig["meta_bundle_identifier"] + + scriptURL + ); - auto env = JSON::Object::Entries {}; - for (const auto& entry : this->userConfig) { - if (entry.first.starts_with("env_")) { - env[entry.first.substr(4)] = entry.second; - } else if (entry.first == "build_env") { - const auto keys = parseStringList(entry.second, { ',', ' ' }); - for (const auto& key : keys) { - env[key] = env::get(key); + auto env = JSON::Object::Entries {}; + for (const auto& entry : this->userConfig) { + if (entry.first.starts_with("env_")) { + env[entry.first.substr(4)] = entry.second; + } else if (entry.first == "build_env") { + const auto keys = parseStringList(entry.second, { ',', ' ' }); + for (const auto& key : keys) { + env[key] = env::get(key); + } } } - } - if (scheme == "npm") { - if (globalUserConfig["meta_bundle_identifier"] == this->userConfig["meta_bundle_identifier"]) { + if (scheme == "npm") { + if (globalUserConfig["meta_bundle_identifier"] == this->userConfig["meta_bundle_identifier"]) { + this->navigator.serviceWorkerServer->container.registerServiceWorker({ + .type = serviceworker::Registration::Options::Type::Module, + .scriptURL = scriptURL, + .scope = scope, + .scheme = scheme, + .serializedWorkerArgs = "", + .id = id + }); + } + } else { + debug("BEFORE REGISTER: %s %s", scheme.c_str(), scope.c_str()); this->navigator.serviceWorkerServer->container.registerServiceWorker({ .type = serviceworker::Registration::Options::Type::Module, .scriptURL = scriptURL, .scope = scope, .scheme = scheme, - .serializedWorkerArgs = "", - .id = id - }); - } - } else { - this->navigator.serviceWorkerServer->container.registerServiceWorker({ - .type = serviceworker::Registration::Options::Type::Module, - .scriptURL = scriptURL, - .scope = scope, - .scheme = scheme, - .serializedWorkerArgs = encodeURIComponent(JSON::Object(JSON::Object::Entries { - {"index", this->client.index}, - {"argv", JSON::Array {}}, - {"env", env}, - {"debug", isDebugEnabled()}, - {"headless", this->userConfig["build_headless"] == "true"}, - {"config", this->userConfig}, - {"conduit", JSON::Object::Entries { + .serializedWorkerArgs = encodeURIComponent(JSON::Object(JSON::Object::Entries { + {"index", this->client.index}, + {"argv", JSON::Array {}}, + {"env", env}, + {"debug", isDebugEnabled()}, + {"headless", this->userConfig["build_headless"] == "true"}, + {"config", this->userConfig}, + {"conduit", JSON::Object::Entries { {"port", this->getRuntime()->services.conduit.port}, {"hostname", this->getRuntime()->services.conduit.hostname}, {"sharedKey", this->getRuntime()->services.conduit.sharedKey} - }} - }).str()), - .id = id - }); + }} + }).str()), + .id = id + }); + } } + debug("REGISTER SCHEME: %s", scheme.c_str()); this->schemeHandlers.registerSchemeHandler(scheme, [this]( auto request, auto bridge, @@ -1094,8 +1106,6 @@ export * from '{{url}}' auto fetch = serviceworker::Request(); SharedPointer serviceWorkerServer = nullptr; - debug("request: %s", request->str().c_str()); - if (window == nullptr) { auto response = SchemeHandlers::Response(request); response.writeHead(400); @@ -1144,8 +1154,8 @@ export * from '{{url}}' .waitForRegistrationToFinish = request->scheme != "npm" }; - debug("fetch: %s", fetch.str().c_str()); - const auto origin = webview::Origin(request->str()); + auto origin = webview::Origin(fetch.url.str()); + origin.scheme = "socket"; debug("origin: %s", origin.name().c_str()); serviceWorkerServer = app->runtime.serviceWorkerManager.get(origin.name()); if (!serviceWorkerServer) { @@ -1158,6 +1168,7 @@ export * from '{{url}}' if (scope.size() > 0) { fetch.url.pathname = scope + fetch.url.pathname; } + debug("fetch: %s", fetch.str().c_str()); const auto fetched = serviceWorkerServer->fetch(fetch, options, [request, callback] (auto res) mutable { if (!request->isActive()) { diff --git a/src/runtime/ipc/routes.cc b/src/runtime/ipc/routes.cc index f5633dbe0..7d7222108 100644 --- a/src/runtime/ipc/routes.cc +++ b/src/runtime/ipc/routes.cc @@ -2406,14 +2406,14 @@ static void mapIPCRoutes (Router *router) { const auto scheme = message.get("scheme"); for (const auto& entry : dynamic_cast(router->bridge).navigator.serviceWorkerServer->container.registrations) { - const auto& scope = entry.first; const auto& registration = entry.second; if (registration.options.scheme == scheme) { auto json = JSON::Object::Entries { {"source", message.name}, {"data", JSON::Object::Entries { {"id", registration.id}, - {"scope", scope}, + {"scope", registration.options.scope}, + {"origin", registration.origin.name()}, {"scheme", scheme}, {"scriptURL", registration.options.scriptURL} }} @@ -2552,15 +2552,16 @@ static void mapIPCRoutes (Router *router) { .type = serviceworker::Registration::Options::Type::Module, .scriptURL = message.get("scriptURL"), .scope = message.get("scope"), - .serializedWorkerArgs = encodeURIComponent(message.get("__runtime_worker_args")) + .scheme = message.get("scheme", "socket"), + .serializedWorkerArgs = encodeURIComponent(message.get("__runtime_worker_args", message.get("serializedWorkerArgs"))) }; const auto url = URL(options.scriptURL); const auto origin = webview::Origin(url.str()); + debug("register: (%s) %s", origin.name().c_str(), url.str().c_str()); auto serviceWorkerServer = app->runtime.serviceWorkerManager.get(origin.name()); if (!serviceWorkerServer) { - debug("no server for: %s", origin.name().c_str()); serviceWorkerServer = dynamic_cast(router->bridge).navigator.serviceWorkerServer; } @@ -2611,10 +2612,10 @@ static void mapIPCRoutes (Router *router) { } const auto scope = message.get("scope"); - + const auto origin = webview::Origin(dynamic_cast(router->bridge).navigator.location.str()); for (const auto& entry : dynamic_cast(router->bridge).navigator.serviceWorkerServer->container.registrations) { const auto& registration = entry.second; - if (scope.starts_with(registration.options.scope)) { + if (registration.options.scheme == "socket" && registration.origin.name() == origin.name() && scope.starts_with(registration.options.scope)) { auto json = JSON::Object { JSON::Object::Entries { {"registration", registration.json()}, diff --git a/src/runtime/serviceworker.hh b/src/runtime/serviceworker.hh index b8cd69425..ee74f6614 100644 --- a/src/runtime/serviceworker.hh +++ b/src/runtime/serviceworker.hh @@ -70,6 +70,7 @@ namespace ssc::runtime::serviceworker { class Registration { public: + static String key (const String&, const URL&, const String& = "socket"); struct Options { enum class Type { Classic, Module }; diff --git a/src/runtime/serviceworker/container.cc b/src/runtime/serviceworker/container.cc index 6fdb4f325..d16ce4a62 100644 --- a/src/runtime/serviceworker/container.cc +++ b/src/runtime/serviceworker/container.cc @@ -47,170 +47,6 @@ namespace ssc::runtime::serviceworker { this->bridge = bridge; this->isReady = true; - auto env = JSON::Object::Entries {}; - for (const auto& entry : bridge->userConfig) { - if (entry.first.starts_with("env_")) { - env[entry.first.substr(4)] = entry.second; - } else if (entry.first == "build_env") { - const auto keys = parseStringList(entry.second, { ',', ' ' }); - for (const auto& key : keys) { - env[key] = env::get(key); - } - } - } - - if (bridge->userConfig["webview_service-workers"].size() > 0) { - const auto scripts = split(bridge->userConfig["webview_service-workers"], " "); - for (const auto& value : scripts) { - auto url = URL( - value, - "socket://" + bridge->userConfig["meta_bundle_identifier"] - ); - - #if SOCKET_RUNTIME_PLATFORM_ANDROID - url.scheme = "https"; - #else - url.scheme = "socket"; - #endif - - const auto scriptURL = url.str(); - const auto parts = split(url.pathname, "/"); - const auto scope = parts.size() > 2 - ? join(Vector(parts.begin(), parts.end() - 1), "/") - : "/"; - const auto id = rand64(); - this->registrations.insert_or_assign(scope, Registration( - id, - Registration::State::Registered, - this->origin, - Registration::Options { - Registration::Options::Type::Module, - scriptURL, - scope, - "*", - encodeURIComponent(JSON::Object(JSON::Object::Entries { - {"index", bridge->index}, - {"env", env}, - {"debug", isDebugEnabled()}, - {"config", bridge->userConfig}, - {"headless", bridge->userConfig["build_headless"] == "true"}, - {"conduit", JSON::Object::Entries { - {"port", this->bridge->getRuntime()->services.conduit.port}, - {"hostname", this->bridge->getRuntime()->services.conduit.hostname}, - {"sharedKey", this->bridge->getRuntime()->services.conduit.sharedKey} - }} - }).str()), - id - } - )); - } - } - - for (const auto& entry : bridge->userConfig) { - const auto& key = entry.first; - const auto& value = entry.second; - - if (key.starts_with("webview_service-workers_")) { - const auto id = rand64(); - const auto scope = normalizeScope(replace(key, "webview_service-workers_", "")); - - #if SOCKET_RUNTIME_PLATFORM_ANDROID - auto scriptURL = String("https://"); - #else - auto scriptURL = String("socket://"); - #endif - scriptURL += bridge->userConfig["meta_bundle_identifier"]; - - if (!value.starts_with("/")) { - scriptURL += "/"; - } - - scriptURL += trim(value); - - this->registrations.insert_or_assign(scope, Registration( - id, - Registration::State::Registered, - this->origin, - Registration::Options { - Registration::Options::Type::Module, - scriptURL, - scope, - "*", - encodeURIComponent(JSON::Object(JSON::Object::Entries { - {"index", bridge->index}, - {"env", env}, - {"debug", isDebugEnabled()}, - {"headless", bridge->userConfig["build_headless"] == "true"}, - {"config", bridge->userConfig}, - {"conduit", JSON::Object::Entries { - {"port", this->bridge->getRuntime()->services.conduit.port}, - {"hostname", this->bridge->getRuntime()->services.conduit.hostname}, - {"sharedKey", this->bridge->getRuntime()->services.conduit.sharedKey} - }} - }).str()), - id - } - )); - } - } - - for (const auto& entry : this->bridge->userConfig) { - const auto& key = entry.first; - if (key.starts_with("webview_protocol-handlers_")) { - const auto scheme = replace(replace(trim(key), "webview_protocol-handlers_", ""), ":", "");; - auto value = entry.second; - if (value.starts_with(".") || value.starts_with("/")) { - if (value.starts_with(".")) { - value = value.substr(1, value.size()); - } - - const auto id = rand64(); - auto parts = split(value, "/"); - parts = Vector(parts.begin(), parts.end() - 1); - auto scope = normalizeScope(join(parts, "/")); - - #if SOCKET_RUNTIME_PLATFORM_ANDROID - auto scriptURL = String("https://"); - #else - auto scriptURL = String("socket://"); - #endif - - scriptURL += bridge->userConfig["meta_bundle_identifier"]; - - if (!value.starts_with("/")) { - scriptURL += "/"; - } - - scriptURL += trim(value); - - this->registrations.insert_or_assign(scope, Registration( - id, - Registration::State::Registered, - this->origin, - Registration::Options { - Registration::Options::Type::Module, - scriptURL, - scope, - scheme, - encodeURIComponent(JSON::Object(JSON::Object::Entries { - {"index", bridge->index}, - {"env", env}, - {"debug", isDebugEnabled()}, - {"config", bridge->userConfig}, - {"headless", bridge->userConfig["build_headless"] == "true"}, - {"conduit", JSON::Object::Entries { - {"port", this->bridge->getRuntime()->services.conduit.port}, - {"hostname", this->bridge->getRuntime()->services.conduit.hostname}, - {"sharedKey", this->bridge->getRuntime()->services.conduit.sharedKey} - }} - }).str()), - id - } - )); - } - } - } - this->bridge->router.map("serviceWorker.fetch.request.body", [this](auto message, auto router, auto reply) mutable { SharedPointer fetch = nullptr; ID id = 0; @@ -445,8 +281,10 @@ namespace ssc::runtime::serviceworker { scope = normalizeScope(scope); - if (this->registrations.contains(scope)) { - const auto& registration = this->registrations.at(scope); + const auto key = Registration::key(scope, this->origin, options.scheme); + debug("KEY: (scope=%s, scheme=%s) %s", scope.c_str(), options.scheme.c_str(), key.c_str()); + if (this->registrations.contains(key)) { + const auto& registration = this->registrations.at(key); if (this->bridge != nullptr) { this->bridge->emit("serviceWorker.register", registration.json(true).str()); @@ -456,7 +294,7 @@ namespace ssc::runtime::serviceworker { } const auto id = options.id > 0 ? options.id : rand64(); - this->registrations.insert_or_assign(scope, Registration( + this->registrations.insert_or_assign(key, Registration( id, Registration::State::Registered, this->origin, @@ -470,7 +308,7 @@ namespace ssc::runtime::serviceworker { } )); - const auto& registration = this->registrations.at(scope); + const auto& registration = this->registrations.at(key); if (this->bridge != nullptr) { this->bridge->emit("serviceWorker.register", registration.json(true)); @@ -484,8 +322,9 @@ namespace ssc::runtime::serviceworker { const auto& scope = normalizeScope(scopeOrScriptURL); const auto& scriptURL = scopeOrScriptURL; + const auto key = Registration::key(scope, this->origin); - if (this->registrations.contains(scope)) { + if (this->registrations.contains(key)) { const auto& registration = this->registrations.at(scope); if (this->bridge != nullptr) { return this->bridge->emit("serviceWorker.unregister", registration.json()); diff --git a/src/runtime/serviceworker/fetch.cc b/src/runtime/serviceworker/fetch.cc index cf68352e4..0442b784c 100644 --- a/src/runtime/serviceworker/fetch.cc +++ b/src/runtime/serviceworker/fetch.cc @@ -49,10 +49,13 @@ namespace ssc::runtime::serviceworker { if (scope.size() > 0) { scope = normalizeScope(scope); + } else { + return false; } - if (scope.size() > 0 && this->container.registrations.contains(scope)) { - auto& registration = this->container.registrations.at(scope); + const auto key = Registration::key(scope, this->container.origin, this->request.scheme); + if (this->container.registrations.contains(key)) { + auto& registration = this->container.registrations.at(key); if ( this->options.waitForRegistrationToFinish && !registration.isActive() && @@ -101,7 +104,8 @@ namespace ssc::runtime::serviceworker { }; if (this->container.protocols.hasHandler(request.scheme)) { - pathname = replace(pathname, scope, ""); + const auto url = URL(scope, this->container.origin); + pathname = replace(pathname, url.pathname, ""); } const auto fetch = JSON::Object::Entries { @@ -118,7 +122,6 @@ namespace ssc::runtime::serviceworker { auto json = registration.json(); json.set("fetch", fetch); - debug("dispatch fetch: %s", json.str().c_str()); return this->container.bridge->emit("serviceWorker.fetch", json); } diff --git a/src/runtime/serviceworker/protocols.cc b/src/runtime/serviceworker/protocols.cc index 0b8d70de7..6399c634e 100644 --- a/src/runtime/serviceworker/protocols.cc +++ b/src/runtime/serviceworker/protocols.cc @@ -73,10 +73,9 @@ namespace ssc::runtime::serviceworker { const String Protocols::getServiceWorkerScope (const String& scheme) { for (const auto& entry : this->container.registrations) { - const auto& scope = entry.first; const auto& registration = entry.second; if (registration.options.scheme == scheme) { - return scope; + return registration.options.scope; } } diff --git a/src/runtime/serviceworker/registration.cc b/src/runtime/serviceworker/registration.cc index 1c16c3ad6..380d04636 100644 --- a/src/runtime/serviceworker/registration.cc +++ b/src/runtime/serviceworker/registration.cc @@ -5,6 +5,15 @@ using ssc::runtime::url::encodeURIComponent; namespace ssc::runtime::serviceworker { + String Registration::key ( + const String& scope, + const URL& origin, + const String& scheme + ) { + auto url = URL(scope, origin); + url.scheme = scheme; + return url.str(); + } Registration::Registration ( const ID id, const State state,