<?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>DEV Community: Geoff</title>
    <description>The latest articles on DEV Community by Geoff (@gapple).</description>
    <link>https://dev.to/gapple</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%2F187199%2F4cc9e50c-7bd4-4066-a1a2-f583787c63e1.jpg</url>
      <title>DEV Community: Geoff</title>
      <link>https://dev.to/gapple</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gapple"/>
    <language>en</language>
    <item>
      <title>Controlling ESP32-C3 Super Mini Onboard LEDs</title>
      <dc:creator>Geoff</dc:creator>
      <pubDate>Thu, 25 Sep 2025 21:52:44 +0000</pubDate>
      <link>https://dev.to/gapple/controlling-esp32-c3-super-mini-onboard-leds-2mmn</link>
      <guid>https://dev.to/gapple/controlling-esp32-c3-super-mini-onboard-leds-2mmn</guid>
      <description>&lt;p&gt;I bought some ESP32 C3 Super Mini boards that have an RGB LED, and information for the onboard LEDs wasn't as easy to find as for the standard ESP32 dev board, so I wanted to consolidate my notes here.&lt;/p&gt;

&lt;p&gt;Standard ESP32 dev boards often have a single LED driven by GPIO2.  As a strapping pin it's pulled high by default, so the led will turn on when the board gets power and will stay on if the pin is not set low. With ESPHome, it's easy to setup the &lt;a href="https://esphome.io/components/status_led/" rel="noopener noreferrer"&gt;Status LED component&lt;/a&gt; which will turn off the LED after boot, and blink it if there is a warning or error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status_led:
  pin:
    number: 2
    ignore_strapping_warning: True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the C3 boards I purchased there is a basic LED and an addressable RGB LED, both connected to pin 8. During boot the RGB LED will flash then turn off, while the basic LED will remain on.  Setting GPIO8 high or low will control the basic LED, so the status LED component can be used by configuring it to the correct pin, but the RGB LED will remain off.&lt;/p&gt;

&lt;p&gt;The RGB light is a WS2812, and can be controlled with the &lt;a href="https://esphome.io/components/light/esp32_rmt_led_strip/" rel="noopener noreferrer"&gt;ESP32 RMT LED Strip component&lt;/a&gt; which works with both the ESP-IDF and Arduino frameworks.  The Arduino framework also has &lt;a href="https://esphome.io/components/light/neopixelbus/" rel="noopener noreferrer"&gt;NeoPixelBus Light&lt;/a&gt; and &lt;a href="https://esphome.io/components/light/fastled/" rel="noopener noreferrer"&gt;FastLED&lt;/a&gt; components as options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;light:
  - platform: esp32_rmt_led_strip
    id: board_rgb_led
    pin:
      number: 8
      ignore_strapping_warning: True
    num_leds: 1
    chipset: WS2812
    rgb_order: GRB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not possible to control both LEDs by just enabling both light components and setting &lt;code&gt;allow_other_uses&lt;/code&gt; for the pin.&lt;/p&gt;




&lt;p&gt;The Status LED component can only be configured to a pin, so can't use the RGB LED.  The &lt;a href="https://esphome.io/components/light/status_led/" rel="noopener noreferrer"&gt;Status LED Light component&lt;/a&gt; which enables controlling the LED when it's not being used to show a warning or error status can be configured to control a binary output component instead of a pin - a &lt;a href="https://esphome.io/components/output/template/" rel="noopener noreferrer"&gt;Template Binary Output component&lt;/a&gt; could use its &lt;code&gt;write_action&lt;/code&gt; trigger to control the RGB light during a warning/error state &lt;a href="https://github.com/esphome/feature-requests/issues/2230" rel="noopener noreferrer"&gt;&lt;sup&gt;[1]&lt;/sup&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another option instead of using the status component is using &lt;code&gt;on_loop&lt;/code&gt; with a custom &lt;a href="https://github.com/esphome/feature-requests/issues/1603#issuecomment-1018994074" rel="noopener noreferrer"&gt;lambda to check for an error state and adjust the RGB LED&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the definition I put together to slow blink yellow on warning, and fast blink red on error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;substitutions:
  status_pin: GPIO8

esphome:
  on_loop:
    then:
      lambda: |-
        static uint32_t last_state = 0;
        auto state = App.get_app_state() &amp;amp; STATUS_LED_MASK;
        if (state != last_state) {
          if (state &amp;amp; STATUS_LED_ERROR) {
            auto call = id(board_led).turn_on();
            call.set_brightness(.2);
            call.set_rgb(1, 0.01, 0.01);
            call.set_effect("fast-pulse");
            call.perform();
          } else if (state &amp;amp; STATUS_LED_WARNING) {
            auto call = id(board_led).turn_on();
            call.set_brightness(.2);
            call.set_rgb(1, 1, 0.01);
            call.set_effect("slow-pulse");
            call.perform();
          } else {
            auto call = id(board_led).turn_off();
            call.perform();
          }
          last_state = state;
        }

light:
  - platform: esp32_rmt_led_strip
    id: board_led
    pin:
      number: ${status_pin}
      ignore_strapping_warning: True
    num_leds: 1
    chipset: WS2812
    rgb_order: GRB
    effects:
      - pulse:
          name: "fast-pulse"
          update_interval: 0.3s
          transition_length: 0.3s
          min_brightness: 15% # Light turns off below 11%
          max_brightness: 50%
      - pulse:
          name: "slow-pulse"
          update_interval: 1s
          transition_length: 1s
          min_brightness: 15%
          max_brightness: 50%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that definition in a separate file, I can easily include it for my projects running on C3 boards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages:
  - !include common/c3-rgb-status.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's &lt;a href="https://github.com/esphome/esphome/pull/5814" rel="noopener noreferrer"&gt;an open PR to implement triggers for status changes&lt;/a&gt;, which would replace the need for &lt;code&gt;on_loop&lt;/code&gt;&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>esphome</category>
    </item>
    <item>
      <title>A Tier List of Drupalcon Swag T-shirts</title>
      <dc:creator>Geoff</dc:creator>
      <pubDate>Tue, 06 Jun 2023 10:55:36 +0000</pubDate>
      <link>https://dev.to/gapple/a-tier-list-of-drupalcon-swag-t-shirts-2pi6</link>
      <guid>https://dev.to/gapple/a-tier-list-of-drupalcon-swag-t-shirts-2pi6</guid>
      <description>&lt;p&gt;A staple of conference swag is the t-shirt, and some companies do it better than others.  With Drupalcon Pittsburgh happening &lt;del&gt;soon&lt;/del&gt; now¹, I thought I would shuffle through my t-shirt collection to rank some of the ones I've gotten at Drupalcons past.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lullabot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CueOKxZw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3dp36fbgnp1fo2evath.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CueOKxZw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3dp36fbgnp1fo2evath.jpg" alt="Lullabot Shirts" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fun, whimsical designs&lt;/li&gt;
&lt;li&gt;Probably won't be presumed as being a 'Bot while wearing these&lt;/li&gt;
&lt;li&gt;Limited quantities, so I missed out on them some years 😔&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A Tier&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Four Kitchens
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GpLT9ipu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2b72dp77dgkk6p9tgyl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GpLT9ipu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2b72dp77dgkk6p9tgyl.jpg" alt="Four Kitchens Shirts" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fun play on the company name&lt;/li&gt;
&lt;li&gt;Theme (usually) tied to the conference location&lt;/li&gt;
&lt;li&gt;There was a good chance of both me and a former manager wearing the same one of these shirts on the same day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A Tier&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Cheeky Monkey Media
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XT5UBM3P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btv41axcw6eqdx2iukcw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XT5UBM3P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btv41axcw6eqdx2iukcw.jpg" alt="Cheeky Monkey Shirt" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A handful of funny or nerdy options were available&lt;/li&gt;
&lt;li&gt;I'd wear the Drupal comm badge even through I'm not a Trekkie (though the graphic is a little too big), but the big slogan on the back takes this shirt out of something I'd wear in public&lt;/li&gt;
&lt;li&gt;The fabric of this shirt does not spark joy&lt;/li&gt;
&lt;li&gt;Not for me, but there's potential&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;B Tier&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Acquia
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ofkFZvq0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w93qx43jxz7q8g5wf0ts.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ofkFZvq0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w93qx43jxz7q8g5wf0ts.jpg" alt="Acquia Shirts" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DC LA shirt has small, unobtrusive design and logo - wouldn't hesitate to wear going about a normal day.&lt;/li&gt;
&lt;li&gt;Slogans are not my thing, but I do like pink&lt;/li&gt;
&lt;li&gt;Sizing on the slogan shirt is &lt;em&gt;very&lt;/em&gt; snug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;B- Tier&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Pantheon
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sn2hdl6Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/352zpfqs3e7q0wk23vr2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sn2hdl6Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/352zpfqs3e7q0wk23vr2.jpg" alt="Pantheon Shirts" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple designs to choose from that change each year&lt;/li&gt;
&lt;li&gt;Wait watching a demo, then wait again to get the shirt&lt;/li&gt;
&lt;li&gt;I don't vibe with the "I Make The Internet" slogan myself, but you do you&lt;/li&gt;
&lt;li&gt;Some creative takes on the company logo, that feel less awkward to wear as someone who has never worked there or used their product&lt;/li&gt;
&lt;li&gt;The company's leadership has &lt;a href="https://arstechnica.com/tech-policy/2023/04/webops-platform-pantheon-defends-hosting-hate-groups-as-developers-quit/"&gt;defended providing services to Alliance Defending Freedom&lt;/a&gt;, which &lt;a href="https://www.splcenter.org/fighting-hate/extremist-files/group/alliance-defending-freedom"&gt;the Southern Poverty Law Center has designated as a hate group for it's actions against LGBTQ+ rights&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;D Tier&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The basic logo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I'll save the individual companies from shame, cause there's &lt;em&gt;a lot&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;I resist wearing shirts with just the logo of companies I've actually worked for² - I'm not going to wear one of an agency I don't, or a product I don't use&lt;/li&gt;
&lt;li&gt;Your marketing dollars are better spent on a shirt that doesn't only get worn when I'm going to get dirty cleaning my bike&lt;/li&gt;
&lt;li&gt;Your company probably has a designer on the team - give them a day to make something fun that people will be excited to wear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;D Tier&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Twilio
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bxzmZB2e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/maa8bvc0hi2g2l6ahm96.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bxzmZB2e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/maa8bvc0hi2g2l6ahm96.jpg" alt="Twilio Shirt" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple illustration related to the company's product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;B- Tier&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Mandrill
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UwrutehN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40b5ydjn02slzlvvgtgp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UwrutehN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40b5ydjn02slzlvvgtgp.jpg" alt="Mandrill Shirt" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's just a logo, but it's colourful and could just be mistaken for something I bought after visiting the zoo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;C+ Tier&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F1GTF3sV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ux9h5c15m4wrqrctrtzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F1GTF3sV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ux9h5c15m4wrqrctrtzt.png" alt="Tier ranking with company logos" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you have a favourite swag shirt from a past conference? Is there a great shirt to get in Pittsburgh?  Some other great swag item you've received at a conference? Tell me about it in the comments.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;¹ I will not be there&lt;br&gt;
² Most logos from any company, really&lt;/p&gt;




&lt;p&gt;And if your team is looking for a Senior Drupal Developer with oddly specific opinions on some niche topics, send me a message.&lt;/p&gt;

</description>
      <category>drupal</category>
    </item>
    <item>
      <title>Analyzing a Content Security Policy circumvention for script injection and data exfiltration</title>
      <dc:creator>Geoff</dc:creator>
      <pubDate>Mon, 19 Sep 2022 07:49:10 +0000</pubDate>
      <link>https://dev.to/gapple/analyzing-a-content-security-policy-circumvention-for-script-injection-and-data-exfiltration-7c0</link>
      <guid>https://dev.to/gapple/analyzing-a-content-security-policy-circumvention-for-script-injection-and-data-exfiltration-7c0</guid>
      <description>&lt;p&gt;In today's news, a particular unsavoury website has had a script injection in place on its website, reportedly for quite some time:&lt;br&gt;
&lt;/p&gt;
&lt;blockquote class="ltag__twitter-tweet"&gt;
    &lt;div class="ltag__twitter-tweet__media ltag__twitter-tweet__media__two-pics"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ab1Eqtvv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/Fc9Tn1sWYAIDF_8.jpg" alt="unknown tweet media content"&gt;
    &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--YqzVFE8a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1566697481337016323/TspMq63p_normal.jpg" alt="Kevin Beaumont profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Kevin Beaumont
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @gossithedog
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      The saga continues - there was (also?) a script injected for a month on Kiwi Farms called Troonshine, gathering information and credentials from user’s systems, posting it to “&lt;a href="https://t.co/XnrUu4t3sd"&gt;poz.hiv&lt;/a&gt;”. &lt;br&gt;&lt;br&gt;They look very, very owned. 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      18:28 PM - 18 Sep 2022
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1571566745638273027" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1571566745638273027" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1571566745638273027" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  What (might have) happened
&lt;/h2&gt;

&lt;p&gt;The provided log entry shows that file with a &lt;code&gt;.opus&lt;/code&gt; extension was requested from the server, with its referrer being the path &lt;code&gt;/test-chat&lt;/code&gt;.  It's likely that it was added to the page as an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; through some other content injection vector.  File extensions are often not particularly meaningful on the web, so the server and browser both can be just fine treating the &lt;code&gt;.opus&lt;/code&gt; file as an HTML page to include as the contents of an inline frame.  &lt;/p&gt;

&lt;p&gt;While the parent page reportedly had a Content Security Policy set via a meta tag that restricted scripts on the page, such a policy &lt;em&gt;does not&lt;/em&gt; apply to the contents of nested iframes, which need their own policy.  As result the iframe is able to load a malicious script from an external domain without restriction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How bad was it?
&lt;/h2&gt;

&lt;p&gt;Pretty bad. But also quite bad that it's just a link in a chain for the full exploit.&lt;br&gt;
Preconditions for the full exploit are being able to (1) upload a file to the website's origin domain and (2) inject an iframe referring to that file elsewhere on the site.&lt;br&gt;&lt;br&gt;
Normally iframe sandboxing restricts accessing the parent page's contents.  However, since the iframe's destination is hosted on the same domain as the parent page it has access to things such as cookies or &lt;code&gt;localStorage&lt;/code&gt;, and can make further requests to the origin domain as the user and extract any data from the responses.  If a session key is accessible (by not being in a http-only cookie), it can be extracted and used to hijack the session, bypassing password and 2FA authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention for savoury people
&lt;/h2&gt;

&lt;p&gt;There's a bunch of measures that could make a vulnerability like this less likely to be exploited on your (savoury) site.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The site admin provided an example of the page's Content Security Policy, which only included a &lt;code&gt;script-src&lt;/code&gt; directive (though not entirely clear if their example was shortened to only what they thought was relevant).  If your site doesn't use iframes, then adding a &lt;code&gt;frame-src: 'none'&lt;/code&gt; directive would prevent a similar content injection from being effective.  Even better would be to start with a restrictive &lt;code&gt;default-src&lt;/code&gt;, and adding exceptions to more specific directives where necessary.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Having the web server add a restrictive policy to the headers served for any file resources - e.g. &lt;code&gt;default-src 'none'; frame-ancestors 'none'&lt;/code&gt;, would prevent them from being embedded elsewhere, or being able to execute any scripts if accessed directly.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If any user-uploaded content is provided through a separate subdomain (e.g. &lt;code&gt;user-content.example.com&lt;/code&gt;), then pages served from it don't have access to cookies restricted to the parent domain, and can't make requests impersonating the user to the main domain.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Content Security Policy directive only including the top level domain (either explicitly or via &lt;code&gt;'self'&lt;/code&gt;) would also not allow any subdomain content from being loaded where not wanted - e.g. &lt;code&gt;default-src 'self'; img-src 'self' user-content.example.com&lt;/code&gt;.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>A helper module for throttling tasks in Drupal</title>
      <dc:creator>Geoff</dc:creator>
      <pubDate>Sat, 26 Dec 2020 07:52:22 +0000</pubDate>
      <link>https://dev.to/gapple/a-helper-module-for-throttling-tasks-in-drupal-3388</link>
      <guid>https://dev.to/gapple/a-helper-module-for-throttling-tasks-in-drupal-3388</guid>
      <description>&lt;p&gt;It is pretty common that a site needs to do something regularly, but not too often.  Most frequently it is maintenance tasks done through hook_cron, like &lt;a href="https://api.drupal.org/api/drupal/core%21modules%21update%21update.module/function/update_cron/9.2.x"&gt;core's update_cron&lt;/a&gt; which limits how often it checks for new versions of a site's modules.  The pattern is pretty simple - store a timestamp in the site's state when the task is executed, then before executing the task again check that the desired period has elapsed.&lt;/p&gt;

&lt;p&gt;A colleague took another approach that I thought was interesting and worth polishing into a contrib module, so I created &lt;a href="https://www.drupal.org/project/periodic"&gt;Periodic&lt;/a&gt;.  Instead of each task needing to duplicate the code for storing and checking a timestamp, Periodic's cron task emits events for common periods of time which other modules can respond to as needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MyModuleEventSubscriber {

  public static function getSubscribedEvents() {
    $events = [];
    $events[PeriodicEvents::HOUR] = ['hourlyTask'];
    $events[PeriodicEvents::DAY] = ['dailyTask'];
    return $events;
  }

  public function hourlyTask() {
    // Do hourly task.
  }

  public function dailyTask() {
    // Do daily task.
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a different interval is needed for a task, Periodic offers a service to check if execution should proceed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function mymodule_cron() {
  $periodicManager = \Drupal::service('periodic.manager');

  // Limit task to every six hours.
  if ($periodicManager-&amp;gt;execute('mymodule.crontask', 21600)) {
    // Do custom task.
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>drupal</category>
    </item>
    <item>
      <title>Inline JavaScript in Drupal 8</title>
      <dc:creator>Geoff</dc:creator>
      <pubDate>Wed, 20 Nov 2019 00:49:27 +0000</pubDate>
      <link>https://dev.to/gapple/inline-javascript-in-drupal-8-4o28</link>
      <guid>https://dev.to/gapple/inline-javascript-in-drupal-8-4o28</guid>
      <description>&lt;h2&gt;
  
  
  A Brief History Inline
&lt;/h2&gt;

&lt;p&gt;In Drupal 7, the global function &lt;code&gt;drupal_add_js()&lt;/code&gt; accepted three types of arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A path to an internal file, or an external URL&lt;/li&gt;
&lt;li&gt;A snippet of JavaScript to be added to the page inline&lt;/li&gt;
&lt;li&gt;An array of values to be added to &lt;code&gt;Drupal.settings&lt;/code&gt; for JavaScript to use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each item had a few properties to determine where on the page it appeared, and what order relative to other items (e.g. &lt;code&gt;['scope' =&amp;gt; 'header', 'group' =&amp;gt; JS_DEFAULT, 'weight' =&amp;gt; -10]&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;drupal_add_js()&lt;/code&gt; could be called at any time during page execution, prior to the page markup being rendered, or render arrays could have the same parameters added to their &lt;code&gt;['#attached']['js']&lt;/code&gt; array.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Drupal 8 Libraries API
&lt;/h2&gt;

&lt;p&gt;Drupal 8 introduced some substantial changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of adding individual files to the page, modules and themes must define libraries - which can contain multiple files.&lt;/li&gt;
&lt;li&gt;Libraries can only be added to the page by specifying them in a render element's &lt;code&gt;['#attached']['library']&lt;/code&gt; array, or via &lt;code&gt;hook_page_attachments()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;There is no API to add inline snippets of JavaScript to the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo5fxn2yycpr1d8twk52j.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo5fxn2yycpr1d8twk52j.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Libraries API offers a number of benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If there was an ordering conflict with JavaScript files in D7, a site would need to implement an alter function to adjust the weights of relevant files to ensure the correct execution order.  Drupal 8 libraries instead each define what other libraries they depend on, allowing Drupal core to automatically resolve the order.&lt;/li&gt;
&lt;li&gt;In D7 all scripts defaulted to being added to the head of the page, since it wasn't possible to determine if libraries like jQuery did not have any other scripts placed in the header dependent on it.  By having D8 libraries define their dependencies, they can default to be included at the bottom of the page markup for better loading performance, but automatically be moved to the header if needed by a dependent library.&lt;/li&gt;
&lt;li&gt;Drupal 8 introduced &lt;a href="https://www.drupal.org/docs/8/core/modules/dynamic-page-cache/overview" rel="noopener noreferrer"&gt;the Dynamic Page Cache&lt;/a&gt;, which caches rendered fragments of the page and improves performance for authenticated users.  If scripts were added separately from render elements, the Dynamic Page Cache wouldn't be able to properly add them when content is retrieved from the cache, resulting in broken functionality.&lt;/li&gt;
&lt;li&gt;The risk of &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting" rel="noopener noreferrer"&gt;Cross-Site Scripting&lt;/a&gt; vulnerabilities can be substantially reduced by blocking inline scripts with a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy" rel="noopener noreferrer"&gt;Content Security Policy&lt;/a&gt;.  If a site's modules all make use of the libraries API &amp;amp; drupalSettings, it's even possible to &lt;a href="https://www.drupal.org/project/csp" rel="noopener noreferrer"&gt;automatically generate a policy for a Drupal site&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding Inline Snippets
&lt;/h2&gt;

&lt;p&gt;If you think you &lt;em&gt;really&lt;/em&gt; need some inline JavaScript it's still possible to add it to a page in a few ways, depending on your needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copy to an external file
&lt;/h3&gt;

&lt;p&gt;If the snippet is static code, and any of it's configuration is included in the snippet and doesn't change based on the page content, it can just be placed in an external file added to the page via the Libraries API. The code will then be aggregated with the page's other JS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refactor to use DrupalSettings
&lt;/h3&gt;

&lt;p&gt;When data needs to be passed from PHP to JS the best solution is to refactor the snippet to work as an external file that uses data provided by &lt;code&gt;drupalSettings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.drupal.org/project/ga" rel="noopener noreferrer"&gt;Googalytics module&lt;/a&gt; is an example of this approach.  Instead of using string concatenation to generate JavaScript to be added as an inline snippet, the method parameters are added to &lt;code&gt;drupalSettings&lt;/code&gt; which is added to the page as a JSON object, then dynamically passed to the relevant JS function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;drupalSettings&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ga&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ga&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drupalSettings&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ga&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;h3&gt;
  
  
  Create a new render element
&lt;/h3&gt;

&lt;p&gt;If the snippet is a repeatable element, create a new renderable element so that you can place it on the page via a render array, and provide any parameters that need to passed to the corresponding Twig template:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$render&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'js_embed'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'#type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'js_embed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'#id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12345&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;js_embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Add to html.html.twig
&lt;/h3&gt;

&lt;p&gt;Snippets can be added directly to Twig templates.  For global JavaScript, just add it to &lt;code&gt;html.html.twig&lt;/code&gt; after &lt;code&gt;&amp;lt;js-placeholder token="{{ placeholder_token }}"&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;js-bottom-placeholder token="{{ placeholder_token }}"&amp;gt;&lt;/code&gt; as required.  If configurable parameters are needed, these can be passed through to the twig template by setting them in &lt;code&gt;hook_preprocess_html()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  HTML Markup
&lt;/h3&gt;

&lt;p&gt;A script tag can be added to the page via the &lt;code&gt;html_tag&lt;/code&gt; render element.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$render&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'snippet'&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="s1"&gt;'#type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'html_tag'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'#tag'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'script'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'#value'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'alert("Hello World!");'&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;or via &lt;code&gt;#markup&lt;/code&gt;&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nv"&gt;$render&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'snippet'&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;br&gt;
  &lt;span class="s1"&gt;'#markup'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;script&amp;gt;alert("Hello World!");&amp;lt;/script&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
  &lt;span class="s1"&gt;'#allowed_tags'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'script'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;];&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  The Attach Inline Module&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Some people &lt;em&gt;still&lt;/em&gt; want core to provide an API to add inline snippets.  At the risk of bolstering those who want this re-introduced to core (which &lt;em&gt;I don't think is a good idea&lt;/em&gt;), I created the &lt;a href="https://www.drupal.org/project/attachinline" rel="noopener noreferrer"&gt;Attach Inline module&lt;/a&gt; to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explore whether a contrib module can sufficiently supply this functionality.&lt;/li&gt;
&lt;li&gt;Better scope the effort required to implement a robust API.&lt;/li&gt;
&lt;li&gt;Enumerate the limitations and trade-offs of this API, to support whether or not it should actually be a candidate for re-inclusion in Drupal core.&lt;/li&gt;
&lt;li&gt;Provide an integration with the &lt;a href="https://www.drupal.org/project/csp" rel="noopener noreferrer"&gt;Content Security Policy module&lt;/a&gt; to limit the risk of using inline scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;The module re-introduces the &lt;code&gt;['#attached']['js']&lt;/code&gt; array to render elements:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nv"&gt;$render&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'element'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;br&gt;
  &lt;span class="s1"&gt;'#attached'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;br&gt;
    &lt;span class="c1"&gt;// Existing Functionality&lt;/span&gt;&lt;br&gt;
    &lt;span class="s1"&gt;'library'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;br&gt;
      &lt;span class="s1"&gt;'drupal/drupalSettings'&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;&lt;br&gt;
    &lt;span class="s1"&gt;'drupalSettings'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'module'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="c1"&amp;gt;// New functionality&amp;lt;/span&amp;gt;
&amp;lt;span class="s1"&amp;gt;'js'&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class="p"&amp;gt;[&amp;lt;/span&amp;gt;
  &amp;lt;span class="s1"&amp;gt;'alert("World");'&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
  &amp;lt;span class="p"&amp;gt;[&amp;lt;/span&amp;gt;
    &amp;lt;span class="s1"&amp;gt;'data'&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class="s1"&amp;gt;'alert("Hello!")'&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
    &amp;lt;span class="s1"&amp;gt;'scope'&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class="s1"&amp;gt;'header'&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
    &amp;lt;span class="s1"&amp;gt;'weight'&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;-&amp;lt;/span&amp;gt;&amp;lt;span class="mi"&amp;gt;10&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
  &amp;lt;span class="p"&amp;gt;],&amp;lt;/span&amp;gt;
&amp;lt;span class="p"&amp;gt;],&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;];&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  The limitations so far&lt;br&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Because inline snippets cannot define their library dependencies directly, if a snippet is added to the page header the dependency resolution cannot determine what libraries should be promoted to the page header.  This could be worked around by having a placeholder library that only defines the dependencies which inline snippets need to be placed in the page head.&lt;/li&gt;
&lt;li&gt;Snippets only have basic weighted ordering, and are placed after all included files.  If an external file requires a snippet to be defined first, this can currently only be accomplished by placing the snippet in the page head and the corresponding library at the end of the page.&lt;/li&gt;
&lt;li&gt;I have no idea if this cooperates with the Dynamic Page Cache or Big Pipe modules, or many other pieces of Drupal core.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>drupal</category>
    </item>
  </channel>
</rss>
