Clerk Auth Redirect/Reload Loop

I am currently looking into alternative forms of user authorization and authentication. Among the solutions I am looking into is Clerk. It’s pretty sweet and has a lot of cool features baked in. (Although the MFA support is a premium add-on and they aren’t super upfront about that).

One of the issues I ran into while implementing Clerk was a redirect loop. I set my home page within the Clerk Dashboard and when I reloaded my app, boom REDIRECTION FOR DAYS. Clerk was continuing to reload the home page for all eternity.

So, I added an onbeforeunload event into the page with a debugger call inside of it. This paused the page in the inspector before it reloaded and allowed me to actually see what was going on.

It turns out that Clerk was outputting an error message into the console. This error message is pictured below:

Clerk Error Message in JS Console

The <SignUp/> and <SignIn/> components cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the Home URL instead. (This notice only appears in development)

🔒 Clerk

Well – ok then. Clerk is redirecting to the Home URL (which is the one it’s already on) and causing a permanent redirect loop. It seems like this would be handled better by simply _not_ loading the SignIn or SignUp components should the conditions for their existence fail.

Hopefully this helps you out! You might consider making your home page and your sign in pages different pages, or conditionally load those components, so that Clerk can be happy and not mess things up.

PHP: What is the difference between .env files and .ini files?

That’s a good question. You might ask this when you are implementing a new project and trying to figure out where to store your secret configuration values.

Before getting into the difference, let’s state the similarities:

  1. Both allow storage of environment specific configuration values and secrets from outside of your codebase.
  2. Both allow retrieval of environment specific configuration values and secrets from outside your codebase.
  3. Both files use a key value pair type format.
  4. Both allow comments within the files.

So what about the differences?

  1. The names are different .ini vs .env
  2. The method of storing the data is different.
  3. The method of retrieving the data is different.
  4. The syntax is different.

That probably doesn’t answer your question though. You likely aren’t interested as much in what the differences are as you are in why you would use one over the other.

Why use .env instead of .ini or .ini instead of .env?

This is likely what you are wanting to know, why would you use one over the other. So let’s list some pro’s and cons of each format to help make the decision.

.env Pros

  • language-agnostic
  • allows referencing other .env values as variables
  • libraries like phpdotenv provide robust functionality

Let’s expand a little on the language-agnostic pro above.

First Scenario. You are coming to PHP from another environment, like Ruby, you are probably wondering where to put your secrets and configuration values for your code. A .env file just makes sense, these are supported in Ruby so there must be a way to add them in PHP. Voila! There is a package that supports .env files that you can easily include in your PHP code and you don’t have to think anymore about it. It probably doesn’t even occur to you to use the build in .ini support.

Second Scenario. You are using both server side JS, like Node, and PHP. You want to share configuration values between the two systems. Both support .env files allowing you to easily share your secrets between both languages.

.ini Pros

  • built-in PHP Support
  • allows grouping of values via sections
  • supports typed values via INI_SCANNER_TYPED
  • allows interpolating other config values and environment variables

.env Cons

  • requires third party library and composer to work
  • features provided change based on library used for loading
  • *some libraries load configuration secrets into globally accessible variables like $_SERVER or $_ENV which will could expose your secrets to code you don’t intend to

.ini Cons

  • requires familiarity with PHP

Which one should I use?

I’m going to give you the answer you don’t want to hear. That’s up to you. It really depends on the needs of your environment and your program.

As far as my personal preferences go. I don’t like including unnecessary dependencies in my codebase. Therefore, if I’m using PHP to develop my app, and I have no need to share my config secrets with other languages, then I would use an .ini file and the built in support for those that PHP provides.

Converting a JavaScript Map to JSON

Ok, let’s cut to the chase. Everyone knows that converting a JavaScript Map to JSON does not work out of the box. By default, the JSON.stringify method will return a {} when converting a Map. It’s super helpful, because it returns an empty object, which is usually precisely what we don’t want. That’s a wee bit frustrating!

How Not to Serialize a Map to JSON

The internet has provided us with the silver bullet, though, right? Object.entries is great and can do all things, right? WRONG. That is wrong and you are wrong if you think that it’s right. You see what I’m getting at?

The existing advice is to use Object.fromEntries and Object.entries in tandem to make it easy to convert a map to and from JSON. The code below shows you this method in all it’s glory.

// ### inspired code below
const mapToSerialize = new Map([[1, 'first key']])
const serializedMap = JSON.stringify(Object.fromEntries(mapToSerialize ))

// now deserialize
const deserializedMap = new Map(Object.entries(JSON.parse(serializedMap)))

Isn’t the code above beautiful? Doesn’t it just wonderfully handle our serialization in the best of ways? No! It doesn’t. Let’s compare the original map to the deserialized map and see how amazing the code works.

The original map looks like this when logged to the console Map(1) {1 => 'first key'}. The deserialized map looks like this when logged to the console: Map(1) {'1' => 'first key'}. Did you spot the difference?

Just to make things clear, let’s try to delete the keyed value out of our Map.

Hey look, deleting that value totally didn’t work

So really, in the end, our serialization using the currently advised method totally works. We serialize our map, deserialize it, and then can’t use it properly. Just as we expected.

NO. It doesn’t work correctly. The difference is that the original map uses an integer key, and the deserialized map uses a string key. When we attempt to delete the value out of the deserialized map, using the key, it fails because the type doesn’t match.

How to Serialize a Map to JSON

I am proposing a different way to serialize a map to JSON.

  1. Create a new array from the existing map
  2. Serialize the array to JSON
  3. Create a new map from the deserialized JSON

The code I propose looks like this. You might not want to keep the logging at the end in your own implementations.

/// ### ugly code below
const mapToSerialize = new Map([[1, 'first key']])

// convert the map to JSON
const arrayToSerialize = []
mapToSerialize.forEach((value, key) => arrayToSerialize.push([key, value]))
const serializedMap = JSON.stringify(arrayToSerialize)

// convert the JSON back to a map
const deserializedMap = new Map(JSON.parse(serializedMap))

// log to console for inspection
console.log(mapToSerialize)
console.log(deserializedMap)

This code correctly works to serialize and deserialize a map to/from JSON.

Look at that, both maps match

In Conclusion

Serializing a Map to and from JSON isn’t as simple as it looks on the outside. Especially if from the outside it looks like it would be super simple. The Object.entries silver bullet only works for Maps with string keys, and attempting to use it for Maps with integer keys will end up with you hating your life. You probably will start listening to Barry Manilow constantly. You might even start eating Quinoa. I’m sure it happens all of the time due to Map serialization woes.

Anyways, if you prefer to not eat Quinoa, then go ahead and convert your Maps to and from JSON the way I suggested above.

That’s all I have to say about that.

POST Body not Included with JavaScript Fetch

I just ran into a problem and I wanted to document it for myself and for anyone else who might have issues. First I describe the problem, then I give the solution. Scroll down if you’re looking for the solution.

The Problem

After posting with JavaScript fetch I did not see the “body” arguments come through on the server. The method of the fetch was set to POST and the body included was an instance of FormData. According to the documentation on MDN, everything should’ve worked. So why wasn’t it working?

The Basic Client Side Code

const body = new FormData(myForm)
// assume myForm.action = "https://example.com/ajax/post"
const response = await fetch(myForm.action, {
	method: "post",
	body,
})

The Basic Server Side Code

<?php
// file: index.php within the ajax/post directory

// don't bother processing the post if there is none
if(empty($_POST)){
	exit;
}

// ... processing code below

I spent some time debugging and without a doubt, every POST request to the index.php file did not have the $_POST array filled out. The POST array was empty as well as the REQUEST array, even the oft-touted file_get_contents('php://input') came up empty.

The Solution

You aren’t going to like it. I don’t like it. The solution to this problem is so annoying that you’ll just facepalm like Picard.

Add a slash to the end of the url you are posting to.

The problem url is: https://example.com/ajax/post
The working url is: https://example.com/ajax/post/

Currently, when this url is posted to, the server responds with a 301 Redirect before the index.php file is hit. But why? The problem is that you do not have a trailing slash in your url. That’s it. You are posting to an index.php file within a directory, but your url does not have a trailing slash. So your server helpfully redirects you to a url with a trailing slash, and you lose your posted information along the way.

Yep, that’s it. Add a trailing slash and you’ll see your body come through when debugging.

RocketChat server not running. Cancelling

As you might know, I’ve set up a RocketChat server recently on Digital Ocean. So far it’s been working great. An update every once and a while is all it needs.

However, yesterday, I attempted an update that failed. From then on every attempted update resulted in “RocketChat server not running. Cancelling”. This was very frutrating.

First, a few commands to try that might help:

  1. systemctl restart rocketchat.service – This will start your RocketChat server in case it is stopped.
  2. systemctl status rocketchat.service – Use this command to check the results of the previous command. Typically this will report that the service is “Active” if the previous command was successful.

In my case, the second command resulted in a “failed” state. The command itself gave me some information as to what the failure was, but not a lot of context as to what caused the failure. However, it did show me the process that it attempted to run. It said, ExecStart=/opt/nvm/versions/node/v14.19.3/bin/node /opt/Rocket.Chat/main.js (code=exited, status=1/FAILURE).

Alright! We’re getting somewhere. With that I was able to figure out what command failed and where that command was run. I navigated directly to the /opt/Rocket.Chat directory which was where the failure was occurring. From here I ran node main.js. The results of this command were much more helpful. They told me this, Error: Cannot find module '@meteorjs/reify/lib/runtime'. That looks like an issue with npm dependencies.

So, I poked around the Rocket.Chat directory structure and looked for dependencies for the Rocket.Chat server. I found what I was looking for in the /opt/Rocket.Chat/programs/server directory.

From this directory I ran two commands

  1. npm install
  2. npm ci

Afterwards I attempted to start the RocketChat server again using the systemctl restart rocketchat.service command. I checked it with systemctl status rocketchat.service and found that it was working now! RocketChat was back to running normally. The problem with “RocketChat server not running. Cancelling” was gone!

Getting Started with Rocket Chat Using Digital Ocean Backing

RocketChat (https://rocket.chat) is a chatting tool similar to MatterMost and Slack. It offers a self-managed community edition as well as some paid plans and is in active development. I’m just learning how to use it, so I’m planning on documenting my journey here.

I installed and set up RocketChat installed in a matter of minutes. The QuickStart guide, though short, includes most of what you need to get up and running.

To get started with your RocketChat instance on DigitalOcean you need to:

  1. Create a DigitalOcean account
  2. Install RocketChat
  3. Create an A Record that points to your Digital Ocean Droplet. *
  4. Setup your Let’s Encrypt certificate.
    rocketchatctl configure --lets-encrypt --root-url=YOUR_DOMAIN --letsencrypt-email=YOUR_EMAIL
  5. Update your RocketChat installation. Run the following commands from your droplet command line interface.
    1. Run sudo rocketchatctl upgrade-rocketchatctl
    2. Run sudo rocketchatctl update

* I use Cloudflare to host my DNS. In this case I had to disable the proxying on the A Record in order to allow RocketChat to work correctly.

Now visit your site (you should not run into any problems) and create your administration account.

One Note

From time to time the Rocket Chat server responds with “Bad Gateway”. I’m not sure exactly why. However, a DigitalOcean droplet can be restarted by using the reboot command from the command line interface.

Git Commits for the Current Branch

I had need to get a list of a simple list of all commits for the Current branch. This approach was simple enough and gave me what I wanted. I modified it to just use HEAD so that I can easily run it against the current branch.

git shortlog --no-merges --graph --abbrev-commit master..HEAD

This came from Alex who posted it on StackOverflow here: https://stackoverflow.com/a/61284456/296889

Change the Display of the Tab Character in PHPStorm

Update 2022-02-18

You can now change the display of the Tab Character directly in the PHPStorm advanced settings. (Thanks to a comment by destinydriven!)

The Tab Character Rendering can be changed in Advanced Settings

Original Article

Recently the way the Tab Character is rendered in PHPStorm was changed. The character used to be rendered in a way that allowed you to see the entire tab character. However, after a recent change the tab character now displays as a single >.

The new tab character is likely a welcome change for some. But there are others of us who really appreciated the old rendering. The new rendering does not indicate how much whitespace is taken by the tab character. For this reason I don’t find it helpful and wanted a way to change it back.

Luckily, recently, the JetBrains team threw in a small registry setting to re-enable to old rendering of tab characters. This setting is available in version 2019.3.2. Access this setting by pressing “Shift” + “Shift” while in PHPStorm. Afterwards search for and select the “Registry…” option.

A Comparison of default tab rendering in PHPStorm
A Comparison of styles – it’s nice to have options.

You’ll find the editor.old.tab.painting option about a quarter of the way down the list. Check it to re-enable the old PHPStorm tab rendering. You can breathe freely now.

The location of the editor.old.tab.painting within the registry.
The editor.old.tab.painting option is about a quarter of the way down the list

SSH Agent Service is Disabled

I’ve just run into an issue while trying to start the ssh-agent in PowerShell Core. The problem displayed was:
unable to start ssh-agent service, error :1058

For my own personal future reference, the problem was that the OpenSSH Authentication Agent service startup type was set to Disabled. The solution was to set the startup type to Automatic.

In Windows 10 you can find the list of services pretty easily. Bring up the start menu and type Services. You’ll see the Services app listed. Open the Services app and you will see a list of services on your machine.

The location of the OpenSSH Authentication Agent within the Services list.
Finding the OpenSSH Authentication Agent

Once the Services app is open, find the OpenSSH Authentication Agent service and set the startup type to Automatic.

💥 You win

PHP Get First and Last Day of Week by Week Number

I’m adding a simple post here with a PHP method that has helped me. This method calculates the beginning and ending of a week given the year and week number. The problem I’ve run into is that “first day of the week” is subjective. Some people believe the first day of the week is “Monday” while others believe the first day of the week is “Sunday”. ISO-8601 specifies the first day of the week as “Monday”. Whereas, most western calendars display Sunday as the first day of the week and Saturday as the last day of the week.

To add to the confusion, PHP’s methods themselves seem confused about what the first and last day of the week are.

For example:

$new_date = new DateTime;
// returns Monday, Jan 29 2018
$new_date->setISODate(2018, 5);

// returns Sunday, Feb 4 2018
$new_date->modify('sunday this week');

// returns Sunday, Jan 28 2018
$new_date->setISODate(2018, 5 ,0);

You’ll notice that the string “sunday this week” actually returns Sunday, Feb 4 whereas setting the date to the 0 day of the same week returns Sunday, Jan 28. I’m not saying that Sunday doesn’t happen twice a week… but Sunday doesn’t happen twice a week.

All this to say, the method below is the one I’ve found returns the most helpful results:

function get_first_and_last_day_of_week( $year_number, $week_number ) {
	// we need to specify 'today' otherwise datetime constructor uses 'now' which includes current time
	$today = new DateTime( 'today' );

	return (object) [
		'first_day' => clone $today->setISODate( $year_number, $week_number, 0 ),
		'last_day'  => clone $today->setISODate( $year_number, $week_number, 6 )
	];
}