Notes to Self

These are notes written to myself. You could generously call it a blog. That's probably reaching.

It’s useful to be able to format a monetary amount so that it formats slightly differently if the value is a whole round number, vs. when it has decimals - ie. if it’s just dollars vs, dollars + cents.

We want it to end looking something like: $64 when it’s a whole number, and $24.56 when it’s not.

In php, to do that, we use fmod to get the modulo of the value, with respect to 1. We can just blindly run a conditional on this to see if it’s a whole number, and just the money_format argument as needed.

$dollarCost = $cost / 100;
$format = '$%i';

if(!fmod($dollarCost, 1)) {
    $format = '$%.0n';
}

return money_format($format, $dollarCost);

VSCode supports Regex matches for find and replace functions.

If you needed to find every instance of something that looked like: import ComponentA from '@some/path/ComponentA'

To be replaced with something similar to: const ComponentA = () => import('@some/path/ComponentA')

The syntax would simply be:

VSCode - Find

import ([A-z]+) from ('[^']+')

VSCode - Replace

const $1 = () => import($2)

You probably want to limit this search to specific paths. We used this find and replace to help a broad update to a large Vue based Single Page App, to help optimise the components for lazy loading.

The raw regex for this might be useful too. It looks broadly the same, but with appropriate delimiters:

/^import ([A-z]+) from ('[^']+')$/const $1 = () => import($2)/

The Problem: You’ve created a Lambda function that you want to run on a CloudFront distribution as a Lambda@Edge function. When you go to create the function you’re hit with a permissions error complaining about the execution role-

Your function’s execution role must be assumable by the edgelambda.amazonaws.com service principle.

The Fix: You need to update the lambda function’s role definition, and explictly add edgelambda.amazonaws.com as a trusted service principle, which do from IAM, modifying the Trust Relationship of the function’s Role.


The fix in actual steps to follow

  1. Jump over the Identity and Access Management (IAM) Service, and then the Roles area.
  2. Find the specific role your lambda function is running under (in our case, we’ve created a new one called Fix-s3-Cors-Vary-Headers)
  3. In the Role, move to the Trust Relationships tab, and then Edit trust relationship
  4. On the Edit Trust Relationship page, you’ll see a Policy Document block.

Modify it so the Statement > Principle > Service in an array that includes the edge lambda service.

{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Principal": {
            "Service": [
               "lambda.amazonaws.com",
               "edgelambda.amazonaws.com"
            ]
         },
         "Action": "sts:AssumeRole"
      }
   ]
}

Note: A default service will have just one record for lambda.amazonaws.com in the Service block, and it will be a single object. Make sure you convert that block into an array to avoid syntax errors.


In Screenshots because the AWS Dashboard is a nightmare

  1. The Error message you’ll see: Error message shown when deploying to labmda@edge

  2. The IAM Roles Area The IAM Roles area

  3. The Specific Role’s Trust Relationship’s tab The IAM Roles area

  4. The Updated (fixed) policy The Fixed trust policy for the lambda role

  5. The Fixed trust relationships, now with the edgelambda.amazonaws.com record included in the Trusted entities list The trust relationships now show the updated record


With the updated trust record you should now be able to assign your Lambda function to run on Lambda@Edge.

AWS S3 buckets should be kept private by default. AWS explictly goes out of it’s way to make it difficult to make a non-private bucket these days.

In most web-based usages of s3 hosted assets, you will need to have some way of access those files. Instead of marking the individual files as public, it’s better to create presigned urls for them, with explict limited expiry times. This allows non-authenticated clients to access specific bucket items for a limited time, with just a url.

The key for this behaviour is the createPresignedRequest method in the SDK.

On the (authenticated) server side, creating a signed url for an s3 object, looks like this in php:

$client = \AWS::createClient('s3');

$cmd = $client->getCommand('GetObject', [
    'Bucket' => $bucket,
    'Key' => $key
]);

$request = $client->createPresignedRequest($cmd, '+20 minutes');

 // Get the actual presigned-url
$presignedUrl = (string) $request->getUri();

The $presignedUrl returns a signed url to access that path, valid for 20 minutes.

It’ll look something like: https://{{ bucket }}/{{ key }}?X-Amz-Content-Sha256=..&X-Amz-Algorithm=..&X-Amz-Credential=..&X-Amz-Date=..&X-Amz-SignedHeaders=host&X-Amz-Expires=..&X-Amz-Signature=..

Laravel Specifically

If you’re using Laravel (or Flysystem more generally) there’s a helper function temporaryUrl on the Storage facade do the the same:

$url = Storage::disk($disk)
    ->temporaryUrl(
        $key,
        Carbon::now()->addMinutes(20)
    );

Bonus Note: Check your path is valid!

Generating the presigned url doesn’t validate the file actually exists in the bucket on the path specified, it’s just signing a request to access that path. If the file isn’t there, the request will 404.

TLDR; Fully Metallic materials in ThreeJS don’t respond to non-directional lights - eg. Ambient or Hemisphere Lights. Adjust the material to be non-metallic, or add Directional lights to the scene


The MeshStandardMaterial in ThreeJS (and all materials that inherit from it - MeshPhysicalMaterial etc.) have a metallness attribute.

The metallness attribute defines how metalic the material should appear. It’s a float, with values from 0 to 1.0. 0 being not metallic at all, 1.0 being fully metallic.

Often when working with scenes, you may see a material showing up as fully black, even when there are lights in the scene. This metallness value might be the cause of that.

In ThreeJs fully metallic materials only respond to non-directional lights. If you’ve only got AmbientLight or HemisphereLight lights lighting your scene, anything with a metallness value of 1.0 will be fully black.

Fully Metallic Black Material A test prop with a material set to be fully metalic, and only AmbientLight lights lighting the scene, leaving a fully black output material

There’s a handful of different ways to fix this, depending on the scene, materials and effect you need.

1. Turn off the metallness on the material

  • Pros It’s simpler to test
  • Cons With only non-direction lights you’ll have a flat looking scene

Often the metallness value might have been mis-set in the original scene. Simply setting it to 0 will allow the material to be lit1:

mesh.material.metallness = 0
mesh.material.needsUpdate = true

Non Metallic with Ambient Lighting Materials adjusted to be non-metalic, with only an AmbientLight lighting the scene

2. Add directional lights to the scene

  • Pros Properly setup lighting will give good looking shadows
  • Cons It’s more involved and expensive on the render

This depends on what your scene needs to look like, but just adding a directional light will show up the material properly. You’ll probably want a handful of lights at different intensities and positions around the center point2.

var light = new THREE.DirectionalLight('#ffe6ae', 5)
light.position.set(7.5, 5, 10)
scene.add(light)

Adding Directional Lighting The same scene, with the materials set to non-metalic, but with 3 DirectionalLight lights added around the object, and shadow maps enabled


  1. Be sure to set needsUpdate to true when modifying any material values otherwise you won’t see the changes. material.needsUpdate = true [return]
  2. Directional Lights default to a target of [0,0,0] unless you set an alternative. [return]

In a ThreeJs scene you might want to lock down certain camera motions.

Using the standard OrbitControls controls system, you’d have something like this setup for your controls in the scene:

controls = new OrbitControls(camera, container)

Controls have 3 basic movements available - zoom, pan, and rotate.

It’s easy to disable each of these directly like so:

controls.enableZoom = false 
controls.enablePan = false 
controls.enableRotate = false 

Disabling all three of the movement types effectively locks the camera in place, which is great if you want to take control of the view for something specific.

Bonus - Disabling controls unbinds any associated input listeners!

The most notable benefit of this - normally with Zoom enabled, the canvas will capture any scroll events and interpret them as zoom in/out motion. For users on a page with a viewer in the content area, this can easily disrupt the scrolling down.

Setting controls.enableZoom = false unbinds the scroll input listeners, so scrolling down a page behaves unaffected by the viewer. Super!

Within Previz we use a custom fragment and vertex shader to handle multiple UV mapping support.

These shaders run in a language called GLSL, which is similar to C/C++, and runs directly on the GPU, and run very close to the metal of the machine with have very limited outputs - they effectively control what the GPU is drawing to the individual pixels and nothing else. Because of that - debugging them is pretty hard. console.log is just a fever dream in this context.

In looking for ways to debug, I ran into an amazing workaround option - instead of trying to output debugging context back to the original context (ie. a browser console or debug hook) overlay debug info on top of the rendered stream.

It uses 2 parts - first a new texture available on the vertex shader which is a character set texture, and secondly a debug function that offsets that texture and draws a debug output onto the output.

Shader Glyfs image

Shader Debug Output

All credit goes go Spektre on this Stack Overflow thread.

The main application running on the kiosks is a VueJS web app, installed as a PWA on the individual tablets. This pairs up to web-views running on a media server, which outputs to a large LED wall.

In this case there are multiple tablets on site, and they will be hot swapped out during the event.

During the actual event - the only visible UI on the tablets is the kiosk application, so we needed an easy way to pro-actively debug and check the state of all the devices without having to physically check each one by one.

For that we built up a quick fleet management system within the app. Each instance of the web app has a built in heartbeat that’s emitted via a web-socket to our management system, and exposes a few helper utility functions.

Instances send a heartbeat every 5 seconds with a payload that looked something like:

{
    msg: 'heartbeat',
    id: '{instance_id}',
    state: '{current_view}'
    meta: { ... }
}