<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Duy K. Bui</title>
    <description>The latest articles on Forem by Duy K. Bui (@khuongduybui).</description>
    <link>https://forem.com/khuongduybui</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F239438%2Fbf5f2081-3ebf-46af-98e4-f72fa4f2d631.jpeg</url>
      <title>Forem: Duy K. Bui</title>
      <link>https://forem.com/khuongduybui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/khuongduybui"/>
    <language>en</language>
    <item>
      <title>Access log middleware for Deno Fresh</title>
      <dc:creator>Duy K. Bui</dc:creator>
      <pubDate>Sun, 16 Oct 2022 22:25:51 +0000</pubDate>
      <link>https://forem.com/khuongduybui/access-log-middleware-for-deno-fresh-3ke7</link>
      <guid>https://forem.com/khuongduybui/access-log-middleware-for-deno-fresh-3ke7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL/DR&lt;/strong&gt;: If you read the title and know exactly what I mean already, simply go to &lt;a href="https://deno.land/x/fresh_logging"&gt;https://deno.land/x/fresh_logging&lt;/a&gt; and follow the README.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is part 3 of a series about my Deno Fresh plugins, so I will not reiterate on the topics already defined in part 1. If you are not interested in my Cloudflare Turnstile plugin, please at least read &lt;a href="https://dev.to/khuongduybui/cloudflare-turnstile-plugin-for-deno-fresh-3ph0#:~:text=by%20all%20means.-,Background,-Alright%2C%20now%20that"&gt;part 1's Background section&lt;/a&gt; if you are unsure what Deno Fresh is.&lt;/p&gt;

&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;I was looking for a Deno middleware to help me easily handle the request's &lt;strong&gt;Accept&lt;/strong&gt; header and couldn't find any. As a good citizen of the open-source community, I set out to create one myself and started reading about Deno middlewares. As soon as I read the &lt;a href="https://fresh.deno.dev/docs/concepts/middleware"&gt;official docs&lt;/a&gt;, however, I realized what the community is missing: an access log middleware! (Side note: as a security software engineer by day, it's my occupational habit to care about access logs.)&lt;/p&gt;

&lt;p&gt;As of this post's writing, my plugin supports the two most commonly used (in my daily work) access log formats: the &lt;a href="%5BLogging%20in%20W3C%20httpd%5D(https://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format)"&gt;W3C Common Log Format&lt;/a&gt; and a variant by Apache called &lt;a href="https://httpd.apache.org/docs/2.4/logs.html#:~:text=%25B%20instead.-,Combined%20Log%20Format,-Another%20commonly%20used"&gt;Combined Log Format&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;By now you should already know the drill.&lt;/p&gt;

&lt;p&gt;Create a Fresh app if you haven't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno run &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; https://fresh.deno.dev sample-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you have created your Fresh app, add &lt;code&gt;fresh-logging&lt;/code&gt; to your &lt;code&gt;import_map.json&lt;/code&gt; for convenience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"$logging/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://deno.land/x/fresh_logging@1.1.2/"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then consume the middleware in your app's &lt;code&gt;routes/_middleware.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$logging/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="c1"&gt;// import { getLogger, ... } from "$logging/index.ts";&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other middlewares&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Use Case 1: Common Log Format
&lt;/h1&gt;

&lt;p&gt;You do not have to do anything, this is the default format.&lt;/p&gt;

&lt;h1&gt;
  
  
  Use Case 2: Apache Combined Log Format
&lt;/h1&gt;

&lt;p&gt;Specify &lt;code&gt;LoggingFormat.APACHE_COMBINED&lt;/code&gt; for the &lt;code&gt;format&lt;/code&gt; option like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoggingFormat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$logging/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoggingFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APACHE_COMBINED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default two headers included are &lt;strong&gt;Referer&lt;/strong&gt; and &lt;strong&gt;User-agent&lt;/strong&gt;. You can override that by optionally providing the &lt;code&gt;combinedHeaders&lt;/code&gt; option, which expects a string array of length 2.&lt;/p&gt;

&lt;h1&gt;
  
  
  Limitations
&lt;/h1&gt;

&lt;p&gt;As of v1.1.2, the following fields are &lt;strong&gt;completely&lt;/strong&gt; omitted (hard-coded to &lt;code&gt;-&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rfc931&lt;/code&gt; (client identifier): not sure how to obtain this&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;authuser&lt;/code&gt; (user identifier): not sure how to obtain this either&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bytes&lt;/code&gt; (response content length): one way I can think of is to use &lt;code&gt;res.clone()&lt;/code&gt; then read its as &lt;code&gt;ArrayBuffer&lt;/code&gt; and get the &lt;code&gt;byteLength&lt;/code&gt;, but that is both &lt;a href="https://github.com/khuongduybui/fresh-logging/issues/1"&gt;time and memory consuming&lt;/a&gt;. Until I can find &lt;a href="https://github.com/denoland/fresh/issues/829"&gt;a more efficient way&lt;/a&gt; to obtain this piece of information, omission is the decision. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users can use the &lt;code&gt;resolvers&lt;/code&gt; to provide custom resolutions of the missing fields. For example, the following code snippet allows logging the response bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ResolutionField&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$logging/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="na"&gt;resolvers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ResolutionField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;byteLength&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, please note that the example above only serves to illustrate how to provide customer resolvers for the missing fields, the actual implementation is sub-optimal. Otherwise, it would have been included as default resolver for that field.&lt;/p&gt;

&lt;h1&gt;
  
  
  Additional Features
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Response Time
&lt;/h2&gt;

&lt;p&gt;Regardless of log format, you can set the option &lt;code&gt;includeDuration&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; to include the handler response time at the end of each log entry. The header &lt;strong&gt;Server-Timing&lt;/strong&gt; will also be set on the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$logging/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;includeDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other middlewares&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: if &lt;code&gt;includeDuration&lt;/code&gt; option is ON, &lt;code&gt;getLogger()&lt;/code&gt; will also count the time taken by all of its subsequent middlewares.&lt;/p&gt;

&lt;p&gt;For example, putting &lt;code&gt;getLogger()&lt;/code&gt; at the beginning of your &lt;code&gt;handler&lt;/code&gt; array will count the time taken by all middlewares, while putting it at the very end of your &lt;code&gt;handler&lt;/code&gt; array will yield the time taken only by the route handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Logger
&lt;/h2&gt;

&lt;p&gt;By default, the middleware uses an &lt;a href="https://deno.land/x/optic@1.3.5"&gt;Optic&lt;/a&gt; logger that sends output to the console at INFO level, with the color, but without the &lt;strong&gt;INFO&lt;/strong&gt; prefix.&lt;/p&gt;

&lt;p&gt;You can provide a log function with the signature &lt;code&gt;(message: string) =&amp;gt; string&lt;/code&gt; to the &lt;code&gt;logger&lt;/code&gt; option to use your own log writing implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$logging/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Optic logging library is full of features (seriously, &lt;a href="https://deno.land/x/optic"&gt;look at it&lt;/a&gt;) and is highly recommended, but you can literally choose any implementation in your custom logger function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Resolver
&lt;/h2&gt;

&lt;p&gt;As briefly discussed in the Limitations section above, you can provide a custom resolver for fields that the middleware is unsure how to populate. The following code snippet illustrates how to resolve the &lt;strong&gt;authuser&lt;/strong&gt; field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ResolutionField&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$logging/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MiddlewareHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MiddlewareHandlerContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="c1"&gt;// inspect req.headers.get("Authorization") and override ctx.state.user&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="na"&gt;resolvers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ResolutionField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authuser&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Appendix: Options
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;getLogger()&lt;/code&gt; function accepts an optional object &lt;code&gt;{}&lt;/code&gt; with the following options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Default Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LoggingFormat.COMMON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The log format to use, defaulting to the &lt;a href="https://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format"&gt;W3C Common Log Format&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utcTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether to log timestamps in UTC or server timezone.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;includeDuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether to include handler response time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resolvers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Selectively supply customer resolvers for the missing fields. See the section on limitations for more details.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;logger&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;console.info&lt;/code&gt; +color -level&lt;/td&gt;
&lt;td&gt;Optionally supply a custom logger function of type &lt;code&gt;(message: string) =&amp;gt; string&lt;/code&gt;. See the section on custom logger for more details.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;combinedHeaders&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;["Referer", "User-agent"]&lt;/td&gt;
&lt;td&gt;Optionally supply custom request headers to include. Requires setting &lt;code&gt;format: LoggingFormat.APACHE_COMBINED&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;That's it, my friends. Thank you for staying with me till the end of it. Feel free to &lt;a href="https://github.com/khuongduybui/fresh-logging/discussions/new"&gt;start a discussion&lt;/a&gt; or &lt;a href="https://github.com/khuongduybui/fresh-logging/issues/new"&gt;file an issue&lt;/a&gt;! You may also hit me up on dev.to messaging anytime too!&lt;/p&gt;

</description>
      <category>deno</category>
      <category>fresh</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cloudflare Turnstile plugin for Deno Fresh (p2)</title>
      <dc:creator>Duy K. Bui</dc:creator>
      <pubDate>Sat, 08 Oct 2022 03:58:16 +0000</pubDate>
      <link>https://forem.com/khuongduybui/cloudflare-turnstile-plugin-for-deno-fresh-p2-29p5</link>
      <guid>https://forem.com/khuongduybui/cloudflare-turnstile-plugin-for-deno-fresh-p2-29p5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL/DR&lt;/strong&gt;: If you read the title and know exactly what I mean already, simply go to &lt;a href="https://deno.land/x/fresh_turnstile"&gt;https://deno.land/x/fresh_turnstile&lt;/a&gt; and follow the README.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is part 2 of a series about my Deno Fresh plugins. If you are not sure about some of the topics mentioned here, you are very likely to find them explained in part 1. In this post, I will continue to discuss the remaining use cases supported by the first release of my Turnstile plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 2: JavaScript-based form submission.
&lt;/h2&gt;

&lt;p&gt;In the previous post, we discussed how to protect a form traditionally submitted. Now imagine the same form, but submitted via AJAX / fetch / REST APIs / GraphQL / etc.&lt;/p&gt;

&lt;p&gt;You can use the implicit rendered &lt;code&gt;&amp;lt;CfTurnstile/&amp;gt;&lt;/code&gt; component similar to use case 1, but this time we will add an ID and a callback property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CfTurnstile&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$turnstile/components/CfTurnstile.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstileElementId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;turnstileToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTurnstileToken&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CfTurnstile&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;turnstileElementId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setTurnstileToken&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By simply adding the callback, you can easily access the token at your &lt;code&gt;turnstileToken&lt;/code&gt; state variable, which will be set as soon as the Turnstile widget decides that the actor is a human.&lt;/p&gt;

&lt;p&gt;However, the token can only be validated at most once, so if the form submission fails for some reason when validating on the server side, you will need a way to retrieve another token. That's where the ID is useful. Get access to the &lt;code&gt;turnstile&lt;/code&gt; object with the &lt;code&gt;getTurnstileAsync&lt;/code&gt; helper function and call reset with the ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTurnstileAsync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$turnstile/plugin.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ... imagine this is inside your form submission action&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;turnstileToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Turnstile is not ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;submit_the_form_with_ajax_or_fetch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// other form fields&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;turnstileToken&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// form submission fails, get a new Turnstile token to be ready for the next try&lt;/span&gt;
  &lt;span class="nx"&gt;setTurnstileToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getTurnstileAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;turnstile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;turnstileElementId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your form is ready for the next submission without having to reload / re-render the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 3: custom interactions.
&lt;/h2&gt;

&lt;p&gt;By now you should have had a fairly good idea on how to do this already, but for completeness's sake let's put it down in writing so that there won't be any misunderstanding.&lt;/p&gt;

&lt;p&gt;Similar to the previous case, use the  component with an ID and a callback. To demonstrate the idea that you can use the component as freely as any preact component you write yourself, I will use a signal in this example instead of a state variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSignal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@preact/signals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CfTurnstile&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$turnstile/components/CfTurnstile.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstileElementId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstileToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CfTurnstile&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;turnstileElementId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;turnstileToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And again, inspect your signal and use the &lt;code&gt;getTurnstileAsync&lt;/code&gt; be able to reset the widget as needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTurnstileAsync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$turnstile/plugin.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ... imagine this is inside your custom interaction&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;turnstileToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Turnstile is not ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;validate_token_on_server_side&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;turnstileToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// validation fails, get a new Turnstile token to be ready for the next try&lt;/span&gt;
  &lt;span class="nx"&gt;turnstileToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getTurnstileAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;turnstile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;turnstileElementId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in order to validate the token, you need to make a call to Turnstile API with your Turnstile site's secret key (see official instructions &lt;a href="https://developers.cloudflare.com/turnstile/get-started/server-side-validation/"&gt;here&lt;/a&gt;). It is not a good idea to retrieve this secret client-side at any point in time. Instead, I highly recommend sending the token to the server side to perform the validation step.&lt;/p&gt;

&lt;h2&gt;
  
  
  BONUS: explicit rendering
&lt;/h2&gt;

&lt;p&gt;The token from Turnstile has a relatively short validity period (5 minutes in v0). Therefore, if you want to use it with a blog post, an email, or an otherwise time-consuming form, you may want to reset the widget right before form submission.&lt;/p&gt;

&lt;p&gt;Alternatively, you may choose to control when the widget gets rendered and time it according to your specificities. The idea is similar to what we've done before: leveraging the &lt;code&gt;useId&lt;/code&gt; hook and the &lt;code&gt;getTurnstileAsync&lt;/code&gt; helper. I am going to show a very simple example resembling use case 1 except that our user needs to click the "Check my form" button first before zhe can "submit" it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@preact/signals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstileElementId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstileToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;turnstileToken&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startTurnstile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnstile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getTurnstileAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;turnstile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;turnstileElementId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;turnstileToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  // other form fields such as name, email, message, etc.
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;turnstileElementId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;startTurnstile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Check my form&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;formReady&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the "check my form" button is clicked, the widget rendering will kick in. Once it has determined that the actor is a human, it will invoke the callback with the token, which is sent to the &lt;code&gt;turnstileToken&lt;/code&gt; signal, which is chained to the &lt;code&gt;formReady&lt;/code&gt; signal, turning the "submit" button from its disabled state into enabled. At this point, you have the token to use dynamically as needed; you also have the hidden input named &lt;code&gt;cf-turnstile-token&lt;/code&gt; inside your &lt;code&gt;&amp;lt;div/&amp;gt;&lt;/code&gt; ready to be submitted alongside your other form fields.&lt;/p&gt;

&lt;p&gt;This approach is very flexible. I'm sure you will find a creative way to solve most of your captcha use cases with this approach if the basic use cases don't fit your bill. Feel free to drop a line and &lt;a href="https://github.com/khuongduybui/fresh-turnstile/discussions/new"&gt;share your insight&lt;/a&gt;, or &lt;a href="https://github.com/khuongduybui/fresh-turnstile/issues/new"&gt;prove me wrong&lt;/a&gt; with an unsolvable use case, I challenge you 😉.&lt;/p&gt;




&lt;p&gt;That's it, my friends. Thank you for staying with me till the end of it. As I've stated in the beginning this is the first release, so I am sure there are a lot more things I have not covered. Feel free to &lt;a href="https://github.com/khuongduybui/fresh-turnstile/discussions/new"&gt;start a discussion&lt;/a&gt; or &lt;a href="https://github.com/khuongduybui/fresh-turnstile/issues/new"&gt;file an issue&lt;/a&gt;! You may also hit me up on dev.to messaging anytime too!&lt;/p&gt;

</description>
      <category>deno</category>
      <category>fresh</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Cloudflare Turnstile plugin for Deno Fresh (p1)</title>
      <dc:creator>Duy K. Bui</dc:creator>
      <pubDate>Fri, 07 Oct 2022 16:04:26 +0000</pubDate>
      <link>https://forem.com/khuongduybui/cloudflare-turnstile-plugin-for-deno-fresh-3ph0</link>
      <guid>https://forem.com/khuongduybui/cloudflare-turnstile-plugin-for-deno-fresh-3ph0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL/DR&lt;/strong&gt;: If you read the title and know exactly what I mean already, simply go to &lt;a href="https://deno.land/x/fresh_turnstile"&gt;https://deno.land/x/fresh_turnstile&lt;/a&gt; and follow the README.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;DISCLAIMER&lt;/strong&gt;: Hi all, this is my first post after 3 years of reading exclusively on dev.to, so all criticisms are welcome! Coincidentally, I'm also writing about my first open-source project release, so again provide feedback by all means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Alright, now that the "noob" disclaimer is out of the way, let me quickly clarify all the nouns I mentioned in the post title:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.cloudflare.com/about-overview/"&gt;Cloudflare&lt;/a&gt;: CDN, DOS-protection, 1.1.1.1, VPN, etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.cloudflare.com/turnstile-private-captcha-alternative/"&gt;Turnstile&lt;/a&gt;: Cloudflare's recently released CAPTCHA alternative.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://deno.land/"&gt;Deno&lt;/a&gt;: The hopeful successor of NodeJS.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fresh.deno.dev/"&gt;Fresh&lt;/a&gt;: Deno's recently released web framework with a focus on &lt;a href="https://jasonformat.com/islands-architecture/"&gt;island architecture&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fresh.deno.dev/docs/concepts/plugins"&gt;plugin&lt;/a&gt;: 3rd-party library to inject CSS and JS into every page in Fresh.&lt;/li&gt;
&lt;li&gt;CAPTCHA (not in the title, but for completeness' sake): a common instrument to guard against machine-driven web interactions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;OK, class is dismissed; you will be quizzed on these terms next week.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Kidding 😂&lt;/p&gt;

&lt;p&gt;Now that we are absolutely on the same page (we are, right?) about what I made, let's talk about how it's supposed to be used.&lt;/p&gt;

&lt;p&gt;In this first release, I'm covering three basic use cases of Turnstile:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Protecting a traditional form submission.&lt;/li&gt;
&lt;li&gt;Protecting a form submitted with AJAX / fetch / etc.&lt;/li&gt;
&lt;li&gt;Protecting a custom interaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Use Case 1: traditional form submission.
&lt;/h2&gt;

&lt;p&gt;Err not so fast, before you can do anything with the plugin you need to have your Turnstile configured, which leads us to ...&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites!
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;You need to give up an email address (to Cloudflare, not me).&lt;/li&gt;
&lt;li&gt;You need a Cloudflare account (not my call, don't argue).&lt;/li&gt;
&lt;li&gt;You need a Turnstile site (free for now, again not my call).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Follow the instructions on the official page and you will be good to go in 30 seconds: &lt;a href="https://www.cloudflare.com/lp/turnstile/"&gt;https://www.cloudflare.com/lp/turnstile/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh, and you also need a Fresh app (not any fresh app, &lt;em&gt;the&lt;/em&gt; Deno Fresh app, ok?).&lt;/p&gt;

&lt;p&gt;Simply run this command to create one if you haven't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno run &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; https://fresh.deno.dev sample-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you have created your Fresh app, add &lt;code&gt;fresh-turnstile&lt;/code&gt; to your &lt;code&gt;import_map.json&lt;/code&gt; for convenience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"$turnstile/"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://deno.land/x/fresh_turnstile@1.0.0-0/"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then consume the plugin in your app's &lt;code&gt;main.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TurnstilePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$turnstile/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nx"&gt;TurnstilePlugin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you are ready to use the plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case 1 (for real this time)
&lt;/h2&gt;

&lt;p&gt;This is the most common use case for Turnstile.&lt;/p&gt;

&lt;p&gt;The basic idea is you have a form, such as a "leave us a note" form, which you only want human beings to be able to submit as that should largely reduce the amount of spam you receive from the form.&lt;/p&gt;

&lt;p&gt;To achieve that with Turnstile, on the client side, you put a Turnstile widget inside your form, which will talk to Turnstile API, do the magic, and decide whether the actor is a human. There are several modes of operation for the widget: interactive, non-interactive, and completely hidden, which you chose when you created the Turnstile site in your Cloudflare dashboard. Once the widget has verified (or it thinks it has) that the actor about to submit the form is a human, it will append a hidden input to your form with a token from Turnstile, which gets included as the form is submitted. Then, on the server side, you can validate the token by making a call to the Turnstile API.&lt;/p&gt;

&lt;p&gt;This plugin provides a &lt;code&gt;&amp;lt;CfTurnstile/&amp;gt;&lt;/code&gt; component for you to use on the client side (fill in &lt;code&gt;sitekey&lt;/code&gt; from your Turnstile site, which can always be retrieved from your Cloudflare dashboard):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CfTurnstile&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$turnstile/components/CfTurnstile.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  // other form fields such as name, email, message, etc.
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CfTurnstile&lt;/span&gt; &lt;span class="na"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the page is fully rendered, &lt;code&gt;&amp;lt;CfTurnstile/&amp;gt;&lt;/code&gt; will be replaced with a DOM tree similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cf-turnstile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;iframe&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- THE WIDGET --&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;iframe&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cf-turnstile-response"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;!-- THE TOKEN --&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the form gets submitted, you can look for &lt;code&gt;cf-turnstile-response&lt;/code&gt; in your route handler and follow &lt;a href="https://developers.cloudflare.com/turnstile/get-started/server-side-validation/"&gt;Turnstile's instructions&lt;/a&gt; for validation.&lt;/p&gt;

&lt;p&gt;Or, if your form is submitted to a POST route, you can use the provided handler generator like this (again, fill in &lt;code&gt;cf_turnstile_secret_key&lt;/code&gt; with your Turnstile site's secret, also retrievable from your Cloudflare dashboard):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CfTurnstileValidationResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;generatePostHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$turnstile/handlers/CfTurnstileValidation.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$flowbite/components/Page.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generatePostHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cf_turnstile_secret_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;CfTurnstileValidation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CfTurnstileValidationResult&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* 3 scenarios can occur here:
   * 1. data is null =&amp;gt; the form was not submitted correctly, or the secret key was not provided.
   * 2. data.success is false =&amp;gt; the form was submitted correctly, but validation failed. data["error-codes"] should be a list of error codes (as strings).
   * 3. data.success is true =&amp;gt; the form was submitted correctly and validated successfully. data.challenge_ts and data.hostname should be available for inspection.
   */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A side note on the interactivity of the widget&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;If you look at the &lt;code&gt;&amp;lt;CfTurnstile/&amp;gt;&lt;/code&gt; component source code, you will not see an IS_BROWSER guard, which means that it will be interactive even outside an island. That might sound like a violation of the island architecture at first, but with implicit rendering, the actual augmentation of the widget on top of the placeholder &lt;code&gt;&amp;lt;div/&amp;gt;&lt;/code&gt; is done by Turnstile's own JavaScript, making it very difficult to render a "disabled" widget.&lt;/p&gt;

&lt;p&gt;Therefore, I made a decision to leave that alone and rely on the plugin consumers' mental efforts to only use this component inside their islands. If you strongly believe that this decision is a mistake, please file an issue on the plugin repo: &lt;br&gt;
&lt;a href="https://github.com/khuongduybui/fresh-turnstile/issues/new"&gt;https://github.com/khuongduybui/fresh-turnstile/issues/new&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;That's it for tonight; thank you very much for your time.&lt;br&gt;
In the following posts, I will discuss the remaining use cases.&lt;/p&gt;

</description>
      <category>fresh</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
