PageSourceSearch

https://railsblocks.com/assets/controllers/turnstile_controller-c9…6bc47805c17452f0fd05abd518b78c66e60b161e.js

js railsblocks.com collected 2026-09-25 20:24:34 UTC 4,902 bytes, 160 lines download raw bytes

1import { Controller } from "@hotwired/stimulus";
2
3const VALID_SIZES = new Set(["normal", "compact", "flexible"]);
4
5export default class extends Controller {
6  static targets = ["widget", "status"];
7
8  connect() {
9    this.loadStartedAt = Date.now();
10    this.renderTurnstile();
11  }
12
13  disconnect() {
14    if (this.widgetId !== undefined && window.turnstile) {
15      try {
16        window.turnstile.remove(this.widgetId);
17      } catch (e) {
18        // Widget might already be removed
19      }
20    }
21
22    this.widgetId = undefined;
23    clearTimeout(this.renderTimeout);
24  }
25
26  renderTurnstile() {
27    if (!this.element.isConnected || this.widgetId !== undefined) return;
28
29    if (typeof window.turnstile === "undefined") {
30      if (Date.now() - this.loadStartedAt > 10000) {
31        this.updateStatus(
32          "The security check is taking longer than expected. Check your connection or content blocker.",
33          "error",
34        );
35      }
36
37      this.renderTimeout = setTimeout(() => this.renderTurnstile(), 250);
38      return;
39    }
40
41    const widget = this.widgetElement;
42    const sitekey = this.element.dataset.sitekey;
43    const theme = this.element.dataset.theme || "auto";
44    const size = this.element.dataset.size;
45    const invisible = size === "invisible";
46    const execution = this.element.dataset.execution || "render";
47    const appearance = this.element.dataset.appearance || (invisible ? "interaction-only" : "always");
48
49    if (invisible) {
50      widget.classList.add("turnstile-invisible");
51    }
52
53    const options = {
54      sitekey,
55      theme,
56      execution,
57      appearance,
58      retry: this.element.dataset.retry || "auto",
59      "retry-interval": Number(this.element.dataset.retryInterval || 3000),
60      "refresh-expired": this.element.dataset.refreshExpired || "auto",
61      "refresh-timeout": this.element.dataset.refreshTimeout || "auto",
62      callback: (token) => {
63        this.token = token;
64        this.updateStatus("Security check complete.", "success");
65        this.element.dispatchEvent(
66          new CustomEvent("turnstile:success", {
67            detail: { token },
68            bubbles: true,
69          }),
70        );
71      },
72      "error-callback": (errorCode) => {
73        this.token = null;
74        this.updateStatus(
75          "The security check had a problem and is retrying automatically.",
76          "error",
77        );
78        this.element.dispatchEvent(
79          new CustomEvent("turnstile:error", {
80            detail: { errorCode },
81            bubbles: true,
82          }),
83        );
84        return false;
85      },
86      "expired-callback": () => {
87        this.token = null;
88        this.updateStatus("Security check expired. Refreshing it now…", "pending");
89        this.element.dispatchEvent(
90          new CustomEvent("turnstile:expired", {
91            bubbles: true,
92          }),
93        );
94      },
95      "timeout-callback": () => {
96        this.token = null;
97        this.updateStatus("Security check timed out. Refreshing it now…", "pending");
98        this.element.dispatchEvent(
99          new CustomEvent("turnstile:timeout", {
100            bubbles: true,
101          }),
102        );
103      },
104    };
105
106    if (!invisible && size && VALID_SIZES.has(size)) {
107      options.size = size;
108    }
109
110    try {
111      this.widgetId = window.turnstile.render(widget, options);
112    } catch (e) {
113      console.error("Turnstile render error:", e);
114      this.updateStatus(
115        "The security check couldn't load. Refresh the page or disable your content blocker.",
116        "error",
117      );
118    }
119  }
120
121  // Method to execute the challenge (for execution mode)
122  execute() {
123    if (this.widgetId !== undefined && window.turnstile) {
124      window.turnstile.execute(this.widgetElement);
125    }
126  }
127
128  // Method to get the current token
129  getToken() {
130    if (this.widgetId !== undefined && window.turnstile) {
131      return window.turnstile.getResponse(this.widgetId);
132    }
133    return null;
134  }
135
136  // Method to reset the widget if needed
137  reset() {
138    if (this.widgetId !== undefined && window.turnstile) {
139      window.turnstile.reset(this.widgetId);
140      this.token = null;
141    }
142  }
143
144  get widgetElement() {
145    return this.hasWidgetTarget ? this.widgetTarget : this.element;
146  }
147
148  updateStatus(message, state) {
149    if (!this.hasStatusTarget) return;
150
151    this.statusTarget.textContent = message;
152    this.statusTarget.dataset.state = state;
153    this.statusTarget.classList.toggle("text-red-600", state === "error");
154    this.statusTarget.classList.toggle("dark:text-red-400", state === "error");
155    this.statusTarget.classList.toggle("text-green-600", state === "success");
156    this.statusTarget.classList.toggle("dark:text-green-400", state === "success");
157    this.statusTarget.classList.toggle("text-neutral-500", state === "pending");
158    this.statusTarget.classList.toggle("dark:text-neutral-400", state === "pending");
159  }
160};

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.