notes.dt.in.th

I added support for external extensions. The settings panel now has an area where extension URLs can be added.

The first external extension I will write will be for uploading files to a custom endpoint. This helps make it easier for me to upload files to my static files hosting.

I put in the extension URL: https://raw.githubusercontent.com/dtinth/tmp-uploader/master/. This effectively installs the extension. The "Uploader" menu now shows up in the files list.

When I click on it, it opens a new browser window and uploads the file to my static files hosting. Then it returns the URL to the image, ready to be copied.

Under the hood

Extension installation

When I put in the extension's URL, tmp.spacet.me goes ahead and fetches manifest file by appending /tmp-manifest.json to the URL. The JSON looks like this:

{
  "name": "dtinth's uploader",
  "description": "Upload files to cloud storage",
  "contributes": {
    "integrations": {
      "uploader": {
        "title": "Uploader",
        "accept": ["*"],
        "url": "https://tmp-uploader.glitch.me/"
      }
    }
  }
}

This caused the "Uploader" to appear in the file's menu. And when clicked, tmp will launch the URL specified.

Uploading process

The first time I go to https://tmp-uploader.glitch.me/, I am shown the Configuration screen, and asked to enter an Upload URL request endpoint. This makes the tool generic and can theoretically but with any host that implements the specified interface.

Now having to configure the URL on every computer may feel cumbersome, but I normally store credentialed URLs like these in my password manager, I know exactly where to recall them should I ever need to.

Now, when a file will be uploaded, the configured endpoint will be called with the filename and MIME type. It returns a JSON that looks like this:

{
  "uploadUrl": "https://s3.ap-southeast-1.amazonaws.com/static.dt.in.th/uploads/2021/01/05/tmp-uploader-result.png?AWSAccessKeyId=...",
  "downloadUrl": "https://static.dt.in.th/uploads/2021/01/05/tmp-uploader-result.png"
}

Then the tmp-uploader will make a PUT request to uploadUrl. Once done the downloadUrl is displayed.

Endpoint implementation

My uploaded files are hosted on Amazon S3. So I created a Lambda function according to the above specification. For uploadUrl, it returns a signed URL that can be used to upload a file to S3 directly from the web browser.

const AWS = require('aws-sdk')
AWS.config.update({ region: process.env.AWS_REGION })
const s3 = new AWS.S3()

exports.handler = async (event) => {
  if (event.queryStringParameters.code !== process.env.API_KEY) {
    return { statusCode: 401, body: 'Unauthorized' }
  }
  const date = new Date().toJSON().slice(0, 10).replace(/\W+/g, '/')
  const key = `uploads/${date}/${event.queryStringParameters.filename}`
  const s3Params = {
    Bucket: 'static.dt.in.th',
    Key: key,
    Expires: 3600,
    ContentType: event.queryStringParameters.type,
  }
  const uploadUrl = await s3.getSignedUrlPromise('putObject', s3Params)
  const downloadUrl = `https://static.dt.in.th/${key}`
  return JSON.stringify({ uploadUrl, downloadUrl })
}

I need to grant the Lambda function essays to the S3 bucket by attaching this policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::static.dt.in.th/*"
    }
  ]
}

and finally I need to allow file uploads from the browser:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT"],
    "AllowedOrigins": ["https://tmp-uploader.glitch.me"],
    "ExposeHeaders": []
  }
]

Other things done

Also implemented rudimentary Rename functionality.