<?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: kieronjmckenna</title>
    <description>The latest articles on Forem by kieronjmckenna (@kieronjmckenna).</description>
    <link>https://forem.com/kieronjmckenna</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%2F440383%2Fe2739546-817f-4397-8273-522b618c6c31.jpg</url>
      <title>Forem: kieronjmckenna</title>
      <link>https://forem.com/kieronjmckenna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kieronjmckenna"/>
    <language>en</language>
    <item>
      <title>How to Create a (Nearly) Free Serverless Rate Limiter on AWS</title>
      <dc:creator>kieronjmckenna</dc:creator>
      <pubDate>Mon, 11 Nov 2024 22:07:50 +0000</pubDate>
      <link>https://forem.com/kieronjmckenna/how-to-create-a-nearly-free-serverless-rate-limiter-on-aws-2j9n</link>
      <guid>https://forem.com/kieronjmckenna/how-to-create-a-nearly-free-serverless-rate-limiter-on-aws-2j9n</guid>
      <description>&lt;p&gt;This post was originally posted on my company's blog &lt;a href="https://blog.opinly.ai/how-to-create-a-nearly-free-serverless-rate-limiter-on-aws/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I recently wanted to implement a rate limiter in typescript for my site &lt;a href="https://opinly.ai" rel="noopener noreferrer"&gt;Opinly&lt;/a&gt; and seeing as we use SST to deploy on AWS, I took a stab at writing one up myself using DynamoDB. Needless to say (considering I'm blogging about it) I'm pretty happy with how it came out.&lt;/p&gt;

&lt;p&gt;There was the option of using Redis on Upstash, but I wasn't keen to bring in a new third party dependency if I didn't have to.&lt;/p&gt;

&lt;p&gt;I've been using DynamoDB for a while and seeing as its scale to zero serverless and decently low latency I thought it would be a great choice. &lt;/p&gt;

&lt;p&gt;I managed to make the whole rate limiter work with only one DynamoDB hit per request, so I've been seeing added latency in the low double digit milliseconds which is acceptable for my use case. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Firstly, some things to note regarding setting this up yourself.&lt;br&gt;
Make sure that your Partition Key, Sort Key, TTL attribute, and Table Name are changed to match your setup.&lt;/p&gt;

&lt;p&gt;This is how my infrastructure as code looks with SST, but its just as easy with CDK or the console&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Moving on to the fun stuff, the real meat of the functionality comes from the UpdateCommand call made to dynamo db&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;What we're doing here is calculating the time period for the counter that we're going to be incrementing.&lt;/p&gt;

&lt;p&gt;Once that is calculated we attempt to increment the count, but we have a condition on the call to ensure that the limit in the database is less than or at the limit we've set in the parameter.&lt;/p&gt;

&lt;p&gt;If the limit is below our rate, then we return false. If not then we will fall into the catch block.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;DynamoDB will throw a specific Error class called ConditionalCheckFailedException only when the condition specified did not match. In our case its&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"attribute_not_exists(#rateLimitCount) OR #rateLimitCount &amp;lt;= :limit"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If it is that specific type of error then we either throw the error specified or return true depending on the parameters.&lt;/p&gt;

&lt;p&gt;In the rare case that dynamo throws a different error then we throw that and let the next guy deal with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;I like the implementation of this rate limiter as its:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very simple&lt;/li&gt;
&lt;li&gt;Doesn't require any third party services&lt;/li&gt;
&lt;li&gt;Can be extended for more functionality such as blocking a user after too many exceeded attempts if needed.&lt;/li&gt;
&lt;li&gt;Low latency -  As I mentioned the single request varies between 5-50ms but this could be improved if you provisioned DynamoDB with the necessary Write Capacity. I just have mine set to pay per request. &lt;/li&gt;
&lt;li&gt;Scalable - Can scale as far as Dynamo can, which is the whole point of the database&lt;/li&gt;
&lt;li&gt;Cheap - We're using 1 RCU per request, and the TTL means that the counters will delete themselves when needed&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>ratelimiting</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>Automating Button Clicks on Websites with Selenium</title>
      <dc:creator>kieronjmckenna</dc:creator>
      <pubDate>Mon, 08 Apr 2024 09:23:59 +0000</pubDate>
      <link>https://forem.com/kieronjmckenna/automating-button-clicks-on-websites-with-selenium-5gh6</link>
      <guid>https://forem.com/kieronjmckenna/automating-button-clicks-on-websites-with-selenium-5gh6</guid>
      <description>&lt;p&gt;Read on to find out how to setup your Python environment and use a script that demonstrates how to use Selenium with Python to automate button clicks on a website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automating Button Clicks on Websites with Selenium
&lt;/h3&gt;

&lt;p&gt;In this blog, we'll explore how to automate the process of clicking buttons on a website using Selenium, a powerful tool for controlling web browsers through programs. We'll write a simple Python script that automates button clicks, a technique useful for a variety of tasks such as testing websites.&lt;/p&gt;

&lt;h3&gt;
  
  
   Setting Up Your Environment for Running Selenium Scripts in Jupyter Notebook
&lt;/h3&gt;

&lt;p&gt;To run Selenium scripts, particularly for automating button clicks on a website using a Jupyter Notebook, you need to set up your environment as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Python: Ensure that Python is installed on your computer. You can download it from the official Python website.&lt;/li&gt;
&lt;li&gt;Install Jupyter Notebook: Jupyter Notebook is an interactive computing environment that enables users to create and share documents containing live code, equations, visualizations, and narrative text. Install it by running pip install notebook in your command line.&lt;/li&gt;
&lt;li&gt;Install Selenium: Selenium is a suite of tools for automating web browsers. Install it using Python’s package manager by running pip install selenium.&lt;/li&gt;
&lt;li&gt;Download WebDriver: For this example, we're using Google Chrome as the browser. Therefore, you need to download ChromeDriver, which is a standalone server that implements WebDriver's wire protocol for Chrome. Ensure the version of ChromeDriver matches your Chrome browser's version.&lt;/li&gt;
&lt;li&gt;Launch Jupyter Notebook: After installation, launch Jupyter Notebook by running jupyter notebook in your command line. This will open the Jupyter Notebook interface in your default web browser.&lt;/li&gt;
&lt;li&gt;Create a New Python Notebook: In the Jupyter Notebook interface, create a new Python notebook where you can write and execute your Selenium script.
This setup provides an interactive and user-friendly environment to run and test your Selenium scripts efficiently.
Writing the Script
Here's a basic script outline:&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Key Points of the Script
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Loop for Repeated Actions: We use a loop to repeat the process.&lt;/li&gt;
&lt;li&gt;Timing: time.sleep(3) sets a delay to prevent rapid-fire requests, which is essential for not overwhelming the server or getting flagged as spam.&lt;/li&gt;
&lt;li&gt;Incognito Mode: Running the browser in incognito mode helps to avoid issues with cached data and cookies.&lt;/li&gt;
&lt;li&gt;Finding Elements: driver.find_element is used to locate the button by its CSS selector. Ensure you have the correct selector for the button you intend to click.&lt;/li&gt;
&lt;li&gt;Error Handling: The try-except block is crucial for handling exceptions and avoiding crashes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Ethical Considerations and Best Practices
&lt;/h3&gt;

&lt;p&gt;Automating interactions with websites should be done responsibly. Abide by the terms of service of the website, and ensure your actions do not harm the website’s functionality or the user experience for others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This script demonstrates a basic yet powerful way to automate browser tasks using Selenium in Python. It can be extended and modified for more complex scenarios and different types of web interactions. Always remember to use such automation responsibly and ethically.&lt;/p&gt;

</description>
      <category>selenium</category>
      <category>python</category>
      <category>webscraping</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Best Way to Use Open AI and Typescript</title>
      <dc:creator>kieronjmckenna</dc:creator>
      <pubDate>Tue, 02 Apr 2024 15:13:00 +0000</pubDate>
      <link>https://forem.com/kieronjmckenna/the-best-way-to-use-open-ai-and-typescript-44fg</link>
      <guid>https://forem.com/kieronjmckenna/the-best-way-to-use-open-ai-and-typescript-44fg</guid>
      <description>&lt;p&gt;(Update April 2024 - when this article was written OpenAI didn't have function calling within tools, read to the end for the updated code)&lt;/p&gt;

&lt;p&gt;In this article you'll find out how to fallback from gpt4 turbo preview to gpt 4 when using function calling (Relevant November 2023 as gpt4-turbo is in preview and heavily rate limited), and with that response validate and type your function calling responses&lt;/p&gt;




&lt;p&gt;The function calling feature is very powerful (we use it all the time to turn unstructured data into structured data) as it allows us to get JSON data for whatever function we want to call. But it's still possible that GPT could hallucinate bad JSON data, or the wrong type, or a wrong field name...I could go on (just last week the JSON coming back couldn't be parsed because it was malformed when telling GPT4 to use emojis). &lt;/p&gt;

&lt;p&gt;Besides those outliers, we want our response from GPT to be typed for use elsewhere in our application rather than just having it as "any".&lt;/p&gt;

&lt;p&gt;So with that context let's get into the code. We're going to use an example where we're taking in a question from a user asking questions on an e-commerce site as a way to show multiple functions we might call based on the user's question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;Real quick install OpenAI:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;and instantiate the OpenAI client:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  GPT4 Turbo to GPT4 Fallback
&lt;/h3&gt;

&lt;p&gt;Now we want to set up the ability to fallback from one GPT model to another. As I mentioned before, it's currently November 2023 and GPT4 Turbo is still in preview and rate limited. So for going to prod, we need to have a fallback to plain old GPT4 😔&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;So just a few try catches and we've got our fallback setup. You could use this for falling back from any model to another. &lt;br&gt;
You might be wondering why I didn't use Omit&amp;lt;..., "model"&amp;gt; rather than passing each of the parameters individually. I tried this, spreading omitted objects into the OpenAI call, but it broke the return type as it uses generics internally based on whether you pass functions in... so try to get that working at your own peril.&lt;/p&gt;

&lt;p&gt;Now, time to make our type-safe call to OpenAI.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;So... a lot of code there, but let's break it down.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;At the top of the file we're defining both the data we want to come back from open ai for each function and a mirror schema in Zod for each type to be passed into our functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We create a map for all the functions and the parser to sanitise the data coming back from OpenAI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We use the function we defined above to make the call with a fallback from GPT4 turbo to regular GPT4. If you're reading this in the future you will most likely not need this, but who knows maybe you'll swap out the models from GPT4 turbo and GPT4 to GPT5 turbo and GPT5&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We use the map we defined to call the function with the data that's sanitised with the appropriate parser. We're using Promise.all here as Open AI just announced that function calling can return multiple function calls, but adjust to your use case&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember this is just an example to show how to make an Open AI calls type-safe and the whole e-comm customer support thing is erroneous. This has been very useful for us when we forgot to mark fields as required in the open AI schema but they came back as undefined, or catching random bad responses from Open AI.&lt;/p&gt;

&lt;p&gt;From there, you're good to integrate OpenAI into your typescript project and be able to sleep at night knowing that the data is at least in the right format as it flows through your program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update April 2024
&lt;/h2&gt;

&lt;p&gt;Since writing the article OpenAI has changed their APIs slightly to move function calling within tools, and we've also figured out how to reduce the duplication between Zod and the JSON schemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating Functions To Tools in OpenAI
&lt;/h3&gt;

&lt;p&gt;Updating our fallback function to using tools looks like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Not much of a change, just moving away from the function calling API to use the new Tools API.&lt;/p&gt;

&lt;p&gt;Now we can change our call to OpenAI:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We're still using the example from above with a few minor changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools API
&lt;/h3&gt;

&lt;p&gt;We're now passing an array of objects that specify the type of tool we're calling. For now, this is always "function" but in the future, there may be options to use data analytics and more.&lt;/p&gt;

&lt;h3&gt;
  
  
   Inferring JSON Schemas from Zod
&lt;/h3&gt;

&lt;p&gt;In the original example, we were writing out the JSON schemas to match our zod parsers, but with the help of &lt;a href="https://github.com/StefanTerdell/zod-to-json-schema" rel="noopener noreferrer"&gt;zod-to-json-schema&lt;/a&gt; we can now infer the descriptions and rules directly from Zod.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>openai</category>
      <category>typescript</category>
      <category>zod</category>
      <category>ai</category>
    </item>
    <item>
      <title>WebP Image Optimisation + BlurHash with Sharp in NodeJS</title>
      <dc:creator>kieronjmckenna</dc:creator>
      <pubDate>Tue, 02 Apr 2024 07:54:06 +0000</pubDate>
      <link>https://forem.com/kieronjmckenna/webp-image-optimisation-blurhash-with-sharp-in-nodejs-f8f</link>
      <guid>https://forem.com/kieronjmckenna/webp-image-optimisation-blurhash-with-sharp-in-nodejs-f8f</guid>
      <description>&lt;p&gt;In this blog post, we'll cover how to convert images into WebP format and create blur hash images to store in your database to show as placeholders. WebP is used to create images that only need to be used in applications.&lt;br&gt;
Resizing to Fit WebP&lt;br&gt;
First, there's an important thing to note when using WebP. There is a maximum width and height of 16383 (according to Google, the creators of WebP - documented here). &lt;br&gt;
If you forget to implement this you'll get a sharp error saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Processed image is too large for the WebP format&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we need to implement code to resize this if necessary before converting:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This code just does a quick check to see if it needs to be resized and returns the sharp instance if needed. &lt;br&gt;
Notice that width and height are optional on the metadata. I haven't seen these values returned as undefined, but in the event an image is corrupted ensure to handle the errors.&lt;br&gt;
Converting To WebP&lt;br&gt;
Next, we convert the image to WebP. We'll make use of the function above to resize if needed.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This returns our converted image to WebP.&lt;br&gt;
For more configuration read more about using Sharp here&lt;br&gt;
Creating the BlurHash&lt;br&gt;
We also want to create a BlurHash to store in our database for showing a placeholder while the image loads over the network.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This returns us the 36-character hashed string that can be used with BlurHash on the front end.&lt;br&gt;
In our react front end we can use it like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Convert to fit your use case, but that's all you need to show blurred placeholders before your already optimised WebP image loads on the front end.&lt;br&gt;
Read more about BlurHash usage here&lt;br&gt;
Thanks for reading&lt;/p&gt;

</description>
      <category>sharp</category>
      <category>node</category>
      <category>typescript</category>
      <category>blurhash</category>
    </item>
    <item>
      <title>Trigger a Typescript AWS Lambda on Receiving an Email with SES</title>
      <dc:creator>kieronjmckenna</dc:creator>
      <pubDate>Tue, 02 Apr 2024 07:47:41 +0000</pubDate>
      <link>https://forem.com/kieronjmckenna/trigger-a-typescript-aws-lambda-on-receiving-an-email-with-ses-1l5m</link>
      <guid>https://forem.com/kieronjmckenna/trigger-a-typescript-aws-lambda-on-receiving-an-email-with-ses-1l5m</guid>
      <description>&lt;p&gt;In this blog post, we’ll discuss how to trigger a Typescript AWS Lambda function when you receive an email in AWS SES. We’ll be using SST V2 in this example, but it’s easily convertible to CDK if needed.&lt;/p&gt;

&lt;p&gt;As a prerequisite, you’ll need to set up a verified identity and inbound rule in Route 53 for this to work. The docs found here are pretty straightforward.&lt;/p&gt;

&lt;p&gt;Infrastructure as Code (IAC)&lt;br&gt;
First, we need to set up the stack and IAC for the inbound rule and function.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Ensure to change the inbound domain and function path to fit your project. This will route all traffic from the domain to your lambda function.&lt;/p&gt;

&lt;p&gt;We also need to go into the console and mark this inbound rule as active. Note you can only have one active rule per region per account — but nothing is stopping you from adding more domains to the recipients array. We’ll cover how to route to your desired operation below.&lt;/p&gt;

&lt;p&gt;AWS Lambda Typescript Handler&lt;br&gt;
Next, we need to set up the handler to perform whatever operation we need.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With this function, we can perform whatever operation we need within our Typescript code.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ses</category>
      <category>typescript</category>
      <category>sst</category>
    </item>
    <item>
      <title>IOS/Android App Store Redirect with AWS CloudFront Functions and CDK</title>
      <dc:creator>kieronjmckenna</dc:creator>
      <pubDate>Wed, 20 Sep 2023 12:32:04 +0000</pubDate>
      <link>https://forem.com/kieronjmckenna/iosandroid-app-store-redirect-with-aws-cloudfront-functions-and-cdk-4g0m</link>
      <guid>https://forem.com/kieronjmckenna/iosandroid-app-store-redirect-with-aws-cloudfront-functions-and-cdk-4g0m</guid>
      <description>&lt;p&gt;I recently wanted to setup a link to redirect people who either clicked on a link or scanned a QR code to be taken to their relevant app stores on IOS or Android for my React Native App. I’m sure theres a few websites out there that do this, but I preferred to have it under my domain, and didn’t want to pay for it.&lt;/p&gt;

&lt;p&gt;I thought I’d give it a go using CloudFront functions. It was a bit tricky to get setup using CDK but I’m happy with how it turned out, so I thought I’d share in case anyone was looking to setup a similar thing.&lt;/p&gt;

&lt;p&gt;You may be thinking, is setting up a whole CDN a bit overkill for a redirect based on a user agent header? Probably. But its not actually that much code , users wont have to wait for a cold-start or experience distance latency, and CloudFront is pay as you go. So why not.&lt;/p&gt;

&lt;p&gt;I’m going to be writing the CDK code in Typescript, and I won’t be diving into setting up Route53, or a CDK project as theres plenty of tutorials out there on those topics.&lt;/p&gt;

&lt;h3&gt;
  
  
   CDK Stack
&lt;/h3&gt;

&lt;p&gt;First thing to note — make sure this stack is deployed in us-east-1 as that’s where CloudFront functions/distributions must be deployed.&lt;/p&gt;

&lt;p&gt;First in the CDK stack we want to add something like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the stack we’re creating a cloudfront distribution, s3 bucket, acm certificate and cloudfront function.&lt;/p&gt;

&lt;p&gt;The bucket doesn’t actually do anything here, but the distribution needs an origin of some kind. Note we’re setting the cloudfront function as the Viewer Request which means it will be triggered before the request ever gets to the origin.&lt;/p&gt;

&lt;p&gt;Since we’ll be setting up the function to redirect the request, the request never reaches the bucket.&lt;/p&gt;

&lt;p&gt;The other key part of the configuration is the line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;originRequestPolicy:  
cloudfront.OriginRequestPolicy.ALL_VIEWER_AND_CLOUDFRONT_2022,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This tells cloudfront to forward headers to our function that we’ll need in order to redirect based on the device type. You can read more about it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/adding-cloudfront-headers.html?source=post_page-----f33612434c24--------------------------------" rel="noopener noreferrer"&gt;AWS CloudFront Headers Docs&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  CloudFront Function
&lt;/h3&gt;

&lt;p&gt;Next we need the js file that we pointed the CloudFront function to in the stack:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Make sure to line up the path to the function with whatever you set it as in the stack. In our example this file is at src/functions/app-store-redirect.js&lt;/p&gt;

&lt;p&gt;The function is making use of the CloudFront headers that we specified to be forwarded to the origin. Customise this to your needs as it can probably be used for more than this simple use case. Make sure to redirect in every scenario unless you’ve setup the bucket to handle requests, otherwise you’ll get an error.&lt;/p&gt;

&lt;h3&gt;
  
  
   Route 53
&lt;/h3&gt;

&lt;p&gt;Finally we need to create a record to tell Route53 to point whatever domain/path we want to the cloudfront distribution.&lt;/p&gt;

&lt;p&gt;I just used the console to create the record using an alias. Read here about how to do that here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html?source=post_page-----f33612434c24--------------------------------" rel="noopener noreferrer"&gt;AWS Route 53 CloudFront Distribution Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And thats it, if users are on an iPhone they’ll be taken to the IOS App Store and if they’re on an Android the Google Play Store.&lt;/p&gt;

&lt;p&gt;Hope you found this useful.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
