fix: make PWA share target functional on Android Chrome (#7468)

Co-authored-by: Zdenek <tvuj-email@example.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Zdenek Stursa
2026-05-10 20:21:16 +02:00
committed by GitHub
parent 9fd3fbca8b
commit 8c06f49b02
4 changed files with 68 additions and 15 deletions

View File

@@ -198,15 +198,32 @@ const recipeUrl = computed({
}
},
get() {
return route.query.recipe_import_url as string | null;
// Prefer the 'url' share field (recipe_import_url, populated by Chrome when
// sharing a page URL). Fall back to the 'text' share field (recipe_import_text)
// for apps that share URLs as plain text, but only when the text value is
// actually a valid http/https URL — shared text can be arbitrary.
const urlFromField = route.query.recipe_import_url as string | null;
if (urlFromField) {
return urlFromField;
}
const textFromField = route.query.recipe_import_text as string | null;
if (textFromField) {
try {
const parsed = new URL(textFromField);
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
return textFromField;
}
}
catch { /* not a URL, ignore */ }
}
return null;
},
});
onMounted(() => {
if (recipeUrl.value && recipeUrl.value.includes("https")) {
// Check if we have a query params for using keywords as tags or staying in edit mode.
// We don't use these in the app anymore, but older automations such as Bookmarklet might still use them,
// and they're easy enough to support.
if (recipeUrl.value) {
// Apply legacy query params for older automations such as the Bookmarklet.
// These are no longer used by the app itself but are easy to keep supporting.
const importKeywordsAsTagsParam = route.query.use_keywords;
if (importKeywordsAsTagsParam === "1") {
importKeywordsAsTags.value = true;
@@ -223,8 +240,9 @@ onMounted(() => {
stayInEditMode.value = false;
}
createByUrl(recipeUrl.value, importKeywordsAsTags.value, false);
return;
// The URL is pre-filled via the recipeUrl computed property.
// Do not auto-submit: the user should review the import options and
// confirm by clicking the submit button.
}
});

View File

@@ -211,7 +211,7 @@
</template>
<script setup lang="ts">
import { useDark, whenever } from "@vueuse/core";
import { useDark, useSessionStorage, whenever } from "@vueuse/core";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { usePasswordField } from "~/composables/use-passwords";
import { alert } from "~/composables/use-toast";
@@ -225,6 +225,7 @@ definePageMeta({
const isDark = useDark();
const router = useRouter();
const route = useRoute();
const i18n = useI18n();
const auth = useMealieAuth();
const { $appInfo, $axios } = useNuxtApp();
@@ -235,6 +236,9 @@ const isFirstLogin = ref(false);
const activityPreferences = useUserActivityPreferences();
const { getDefaultActivityRoute } = useDefaultActivity();
// Survives the page reload that happens during OIDC redirect
const pendingShareRedirect = useSessionStorage<string | null>("pwa_share_redirect", null);
useSeoMeta({
title: i18n.t("user.login"),
});
@@ -259,14 +263,29 @@ useAsyncData(useAsyncKey(), async () => {
whenever(
() => loggedIn.value && groupSlug.value,
() => {
// First-login setup always takes priority
if (!isDemo.value && isFirstLogin.value && auth.user.value?.admin) {
router.push("/admin/setup");
return;
}
// After login, honour a pending PWA share redirect.
// The redirect param can arrive via the URL query string (password login)
// or via sessionStorage (OIDC login, where the OIDC provider reload clears
// the query string).
const redirectFromQuery = route.query.redirect as string | undefined;
const redirectTarget = redirectFromQuery ?? pendingShareRedirect.value;
if (redirectTarget && redirectTarget.startsWith("/")) {
pendingShareRedirect.value = null;
router.push(redirectTarget);
return;
}
const defaultActivityRoute = getDefaultActivityRoute(
activityPreferences.value.defaultActivity,
groupSlug.value,
);
if (!isDemo.value && isFirstLogin.value && auth.user.value?.admin) {
router.push("/admin/setup");
}
else if (defaultActivityRoute) {
if (defaultActivityRoute) {
router.push(defaultActivityRoute);
}
else {
@@ -316,6 +335,13 @@ async function oidcAuthenticate(callback = false) {
oidcLoggingIn.value = false;
}
else {
// Save any pending PWA share redirect before leaving for the OIDC provider.
// The OIDC callback reloads the page, which clears the query string, so we
// persist the target in sessionStorage and restore it after login.
const redirectTarget = route.query.redirect as string | undefined;
if (redirectTarget && redirectTarget.startsWith("/")) {
pendingShareRedirect.value = redirectTarget;
}
navigateTo("/api/auth/oauth", { external: true }); // start the redirect process
}
}