Browse Source
			
			
			
			
				
		Reviewed-on: https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/pulls/139
				 29 changed files with 509 additions and 154 deletions
			
			
		@ -0,0 +1,227 @@ | 
				
			|||
<template> | 
				
			|||
  <!-- CONTENT --> | 
				
			|||
  <section id="Content" class="relative w-[100vw] h-[100vh]"> | 
				
			|||
    <div | 
				
			|||
      class="p-6 bg-white w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto" | 
				
			|||
    > | 
				
			|||
      <div class="mb-4"> | 
				
			|||
        <h1 class="text-xl text-center font-semibold relative mb-4"> | 
				
			|||
          Redirecting to Time Safari | 
				
			|||
        </h1> | 
				
			|||
 | 
				
			|||
        <div v-if="destinationUrl" class="space-y-4"> | 
				
			|||
          <!-- Platform-specific messaging --> | 
				
			|||
          <div class="text-center text-gray-600 mb-4"> | 
				
			|||
            <p v-if="isMobile"> | 
				
			|||
              {{ | 
				
			|||
                isIOS | 
				
			|||
                  ? "Opening Time Safari app on your iPhone..." | 
				
			|||
                  : "Opening Time Safari app on your Android device..." | 
				
			|||
              }} | 
				
			|||
            </p> | 
				
			|||
            <p v-else>Opening Time Safari app...</p> | 
				
			|||
            <p class="text-sm mt-2"> | 
				
			|||
              <span v-if="isMobile" | 
				
			|||
                >If the app doesn't open automatically, use one of these | 
				
			|||
                options:</span | 
				
			|||
              > | 
				
			|||
              <span v-else>Choose how you'd like to open this link:</span> | 
				
			|||
            </p> | 
				
			|||
          </div> | 
				
			|||
 | 
				
			|||
          <!-- Deep Link Button --> | 
				
			|||
          <div class="text-center"> | 
				
			|||
            <a | 
				
			|||
              :href="deepLinkUrl || '#'" | 
				
			|||
              class="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors" | 
				
			|||
              @click="handleDeepLinkClick" | 
				
			|||
            > | 
				
			|||
              <span v-if="isMobile">Open in Time Safari App</span> | 
				
			|||
              <span v-else>Try Opening in Time Safari App</span> | 
				
			|||
            </a> | 
				
			|||
          </div> | 
				
			|||
 | 
				
			|||
          <!-- Web Fallback Link --> | 
				
			|||
          <div class="text-center"> | 
				
			|||
            <a | 
				
			|||
              :href="webUrl || '#'" | 
				
			|||
              target="_blank" | 
				
			|||
              class="inline-block bg-gray-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-gray-700 transition-colors" | 
				
			|||
              @click="handleWebFallbackClick" | 
				
			|||
            > | 
				
			|||
              <span v-if="isMobile">Open in Web Browser Instead</span> | 
				
			|||
              <span v-else>Open in Web Browser</span> | 
				
			|||
            </a> | 
				
			|||
          </div> | 
				
			|||
 | 
				
			|||
          <!-- Manual Instructions --> | 
				
			|||
          <div class="text-center text-sm text-gray-500 mt-4"> | 
				
			|||
            <p v-if="isMobile"> | 
				
			|||
              Or manually open: | 
				
			|||
              <code class="bg-gray-100 px-2 py-1 rounded">{{ | 
				
			|||
                deepLinkUrl | 
				
			|||
              }}</code> | 
				
			|||
            </p> | 
				
			|||
            <p v-else> | 
				
			|||
              If you have the Time Safari app installed, you can also copy this | 
				
			|||
              link: | 
				
			|||
              <code class="bg-gray-100 px-2 py-1 rounded">{{ | 
				
			|||
                deepLinkUrl | 
				
			|||
              }}</code> | 
				
			|||
            </p> | 
				
			|||
          </div> | 
				
			|||
 | 
				
			|||
          <!-- Platform info for debugging --> | 
				
			|||
          <div | 
				
			|||
            v-if="isDevelopment" | 
				
			|||
            class="text-center text-xs text-gray-400 mt-4" | 
				
			|||
          > | 
				
			|||
            <p> | 
				
			|||
              Platform: {{ isMobile ? (isIOS ? "iOS" : "Android") : "Desktop" }} | 
				
			|||
            </p> | 
				
			|||
            <p>User Agent: {{ userAgent.substring(0, 50) }}...</p> | 
				
			|||
          </div> | 
				
			|||
        </div> | 
				
			|||
 | 
				
			|||
        <div v-else-if="pageError" class="text-center text-red-500 mb-4"> | 
				
			|||
          {{ pageError }} | 
				
			|||
        </div> | 
				
			|||
 | 
				
			|||
        <div v-else class="text-center text-gray-600"> | 
				
			|||
          <p>Processing redirect...</p> | 
				
			|||
        </div> | 
				
			|||
      </div> | 
				
			|||
    </div> | 
				
			|||
  </section> | 
				
			|||
</template> | 
				
			|||
 | 
				
			|||
<script lang="ts"> | 
				
			|||
import { Component, Vue } from "vue-facing-decorator"; | 
				
			|||
import { RouteLocationNormalizedLoaded, Router } from "vue-router"; | 
				
			|||
 | 
				
			|||
import { APP_SERVER } from "@/constants/app"; | 
				
			|||
import { logger } from "@/utils/logger"; | 
				
			|||
import { errorStringForLog } from "@/libs/endorserServer"; | 
				
			|||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; | 
				
			|||
 | 
				
			|||
@Component({}) | 
				
			|||
export default class DeepLinkRedirectView extends Vue { | 
				
			|||
  $router!: Router; | 
				
			|||
  $route!: RouteLocationNormalizedLoaded; | 
				
			|||
  pageError: string | null = null; | 
				
			|||
  destinationUrl: string | null = null; // full path after "/deep-link/" | 
				
			|||
  deepLinkUrl: string | null = null; // mobile link starting "timesafari://" | 
				
			|||
  webUrl: string | null = null; // web link, eg "https://timesafari.app/..." | 
				
			|||
  isDevelopment: boolean = false; | 
				
			|||
  userAgent: string = ""; | 
				
			|||
  private platformService = PlatformServiceFactory.getInstance(); | 
				
			|||
 | 
				
			|||
  mounted() { | 
				
			|||
    // Get the path from the route parameter (catch-all parameter) | 
				
			|||
    const pathParam = this.$route.params.path; | 
				
			|||
 | 
				
			|||
    // If pathParam is an array (catch-all parameter), join it | 
				
			|||
    const fullPath = Array.isArray(pathParam) ? pathParam.join("/") : pathParam; | 
				
			|||
     | 
				
			|||
    // Get query parameters from the route | 
				
			|||
    const queryParams = this.$route.query; | 
				
			|||
     | 
				
			|||
    // Build query string if there are query parameters | 
				
			|||
    let queryString = ""; | 
				
			|||
    if (Object.keys(queryParams).length > 0) { | 
				
			|||
      const searchParams = new URLSearchParams(); | 
				
			|||
      Object.entries(queryParams).forEach(([key, value]) => { | 
				
			|||
        if (value !== undefined && value !== null) { | 
				
			|||
          const stringValue = Array.isArray(value) ? value[0] : value; | 
				
			|||
          if (stringValue !== null && stringValue !== undefined) { | 
				
			|||
            searchParams.append(key, stringValue); | 
				
			|||
          } | 
				
			|||
        } | 
				
			|||
      }); | 
				
			|||
      queryString = "?" + searchParams.toString(); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    // Combine path with query parameters | 
				
			|||
    const fullPathWithQuery = fullPath + queryString; | 
				
			|||
     | 
				
			|||
    this.destinationUrl = fullPathWithQuery; | 
				
			|||
    this.deepLinkUrl = `timesafari://${fullPathWithQuery}`; | 
				
			|||
    this.webUrl = `${APP_SERVER}/${fullPathWithQuery}`; | 
				
			|||
 | 
				
			|||
    this.isDevelopment = process.env.NODE_ENV !== "production"; | 
				
			|||
    this.userAgent = navigator.userAgent; | 
				
			|||
 | 
				
			|||
    this.openDeepLink(); | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  private openDeepLink() { | 
				
			|||
    if (!this.deepLinkUrl || !this.webUrl) { | 
				
			|||
      this.pageError = | 
				
			|||
        "No deep link was provided. Check the URL and try again."; | 
				
			|||
      return; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    try { | 
				
			|||
      // For mobile, try the deep link URL; for desktop, use the web URL | 
				
			|||
      const redirectUrl = this.isMobile ? this.deepLinkUrl : this.webUrl; | 
				
			|||
 | 
				
			|||
      // Method 1: Try window.location.href (works on most browsers) | 
				
			|||
      window.location.href = redirectUrl; | 
				
			|||
 | 
				
			|||
      // Method 2: Fallback - create and click a link element | 
				
			|||
      setTimeout(() => { | 
				
			|||
        try { | 
				
			|||
          const link = document.createElement("a"); | 
				
			|||
          link.href = redirectUrl; | 
				
			|||
          link.style.display = "none"; | 
				
			|||
          document.body.appendChild(link); | 
				
			|||
          link.click(); | 
				
			|||
          document.body.removeChild(link); | 
				
			|||
        } catch (error) { | 
				
			|||
          logger.error( | 
				
			|||
            "Fallback deep link failed: " + errorStringForLog(error), | 
				
			|||
          ); | 
				
			|||
          this.pageError = | 
				
			|||
            "Redirecting to the Time Safari app failed. Please use a manual option below."; | 
				
			|||
        } | 
				
			|||
      }, 100); | 
				
			|||
    } catch (error) { | 
				
			|||
      logger.error("Deep link redirect failed: " + errorStringForLog(error)); | 
				
			|||
      this.pageError = | 
				
			|||
        "Unable to open the Time Safari app. Please use a manual option below."; | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  private handleDeepLinkClick(event: Event) { | 
				
			|||
    if (!this.deepLinkUrl) return; | 
				
			|||
 | 
				
			|||
    // Prevent default to handle the click manually | 
				
			|||
    event.preventDefault(); | 
				
			|||
 | 
				
			|||
    this.openDeepLink(); | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  private handleWebFallbackClick(event: Event) { | 
				
			|||
    if (!this.webUrl) return; | 
				
			|||
 | 
				
			|||
    // Get platform capabilities | 
				
			|||
    const capabilities = this.platformService.getCapabilities(); | 
				
			|||
 | 
				
			|||
    // For mobile, try to open in a new tab/window | 
				
			|||
    if (capabilities.isMobile) { | 
				
			|||
      event.preventDefault(); | 
				
			|||
      window.open(this.webUrl, "_blank"); | 
				
			|||
    } | 
				
			|||
    // For desktop, let the default behavior happen (opens in same tab) | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  // Computed properties for template | 
				
			|||
  get isMobile(): boolean { | 
				
			|||
    return this.platformService.getCapabilities().isMobile; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  get isIOS(): boolean { | 
				
			|||
    return this.platformService.getCapabilities().isIOS; | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
</script> | 
				
			|||
					Loading…
					
					
				
		Reference in new issue