1import { Controller } from "@hotwired/stimulus"; 2 3// Google One Tap (Identity Services) â auto prompt for signed-out visitors. 4export default class extends Controller { 5 static values = { 6 clientId: String, 7 callbackUrl: String, 8 enabled: { type: Boolean, default: false }, 9 }; 10 11 connect() { 12 if (!this.enabledValue || !this.clientIdValue) return; 13 this.loadScript() 14 .then(() => this.prompt()) 15 .catch(() => {}); 16 } 17 18 loadScript() { 19 if (window.google?.accounts?.id) return Promise.resolve(); 20 21 return new Promise((resolve, reject) => { 22 const existing = document.querySelector("script[data-google-gsi]"); 23 if (existing) { 24 existing.addEventListener("load", () => resolve()); 25 existing.addEventListener("error", reject); 26 return; 27 } 28 29 const script = document.createElement("script"); 30 script.src = "https://accounts.google.com/gsi/client"; 31 script.async = true; 32 script.defer = true; 33 script.dataset.googleGsi = "true"; 34 script.addEventListener("load", () => resolve()); 35 script.addEventListener("error", reject); 36 document.head.appendChild(script); 37 }); 38 } 39 40 prompt() { 41 if (!window.google?.accounts?.id) return; 42 43 window.google.accounts.id.initialize({ 44 client_id: this.clientIdValue, 45 callback: (response) => this.handleCredential(response), 46 cancel_on_tap_outside: false, 47 context: "signin", 48 itp_support: true, 49 use_fedcm_for_prompt: true, 50 }); 51 52 window.google.accounts.id.prompt(); 53 } 54 55 async handleCredential(response) { 56 if (!response?.credential) return; 57 58 const csrf = document.querySelector('meta[name="csrf-token"]')?.content; 59 const res = await fetch(this.callbackUrlValue, { 60 method: "POST", 61 headers: { 62 "Content-Type": "application/json", 63 Accept: "application/json", 64 "X-CSRF-Token": csrf || "", 65 }, 66 credentials: "same-origin", 67 body: JSON.stringify({ credential: response.credential }), 68 }); 69 70 if (!res.ok) return; 71 72 const data = await res.json().catch(() => ({})); 73 const redirectTo = data.redirect_to || "/"; 74 if (window.Turbo) { 75 Turbo.visit(redirectTo); 76 } else { 77 window.location.assign(redirectTo); 78 } 79 } 80};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.