12 changed files with 297 additions and 73 deletions
			
			
		@ -0,0 +1,221 @@ | 
				
			|||||
 | 
					<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; | 
				
			||||
 | 
					    this.destinationUrl = fullPath; | 
				
			||||
 | 
					    this.deepLinkUrl = `timesafari://${fullPath}`; | 
				
			||||
 | 
					    this.webUrl = `${APP_SERVER}/${fullPath}`; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // Log for debugging | 
				
			||||
 | 
					    logger.info("Deep link processing:", { | 
				
			||||
 | 
					      fullPath, | 
				
			||||
 | 
					      deepLinkUrl: this.deepLinkUrl, | 
				
			||||
 | 
					      webUrl: this.webUrl, | 
				
			||||
 | 
					      userAgent: this.userAgent, | 
				
			||||
 | 
					    }); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    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; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    logger.info("Attempting deep link redirect:", { | 
				
			||||
 | 
					      deepLinkUrl: this.deepLinkUrl, | 
				
			||||
 | 
					      webUrl: this.webUrl, | 
				
			||||
 | 
					      isMobile: this.isMobile, | 
				
			||||
 | 
					      userAgent: this.userAgent, | 
				
			||||
 | 
					    }); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    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); | 
				
			||||
 | 
					          logger.info("Fallback link click completed"); | 
				
			||||
 | 
					        } 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