UPM Server Running in AWS Lambda

Recently, it came to my attention that one of my most beloved games, VRChat, was transitioning to a proprietary piece of software to manage the installation of their SDK, yet another piece of software to install to achieve the same results as before 🙄

However, given that it shares a format very similar to Unity’s own built-in packages format, and simply uses a different process to install them, I wondered if I could utilize Unity’s own built-in package management system and a scoped registry hosted on a custom domain to achieve the same results.


Endpoints

Now, UPM (Unity Package Manager) shares very similar API endpoints as the NPM Registry, namely

GET /-/v1/search
GET /{package}

While these endpoints are moderately well documented for NPM, they’re terribly documented for UPM, forcing me to query existing UPM-Compatible APIs in an attempt to find the response JSON format required by Unity. It’s *similar* to NPM but not quite the same.


Search

The following is an example of the search endpoint’s required formatting

{
    "objects": [
        {
            "package": {
                "author": {
                    "email": "[email protected]",
                    "name": "Example Name",
                    "url": "https://example.com"
                },
                "keywords": [
                    "example",
                    "keywords"
                ],
                "links": {
                    "npm": ""
                },
                "name": "com.example.package",
                "publisher": {},
                "scope": "",
                "version": "1.0.0"
            },
            "score": {
                "detail": {
                    "maintenance": 0,
                    "popularity": 0,
                    "quality": 0
                },
                "final": 0,
            },
            "searchScore": 1
        }
    ],
    "total": 1,
    "time": "Wed Jan 25 2017 19:23:35 GMT+0000 (UTC)"
}

This is pretty much identical to the regular NPM results , though I have zeroed-out and left empty strings where no data is actually read but is seemingly required by UPM to successfully parse the response.


Package

The following is an example of the response required by UPM for the package metadata endpoint

{
    "dist-tags": {
        "latest": "0.0.1"
    },
    "name": "com.example.package",
    "readme": "Readme description for example package.",
    "time": {
        "0.0.1": "Wed Jan 25 2017 19:23:35 GMT+0000 (UTC)"
    },
    "users": {},
    "versions": {
        "0.0.1": {
            "author": {
                "email": "[email protected]",
                "name": "Example Name",
                "url": "https://example.com"
            },
            "dependencies": {
                "com.example.dependency": "0.0.2"
            },
            "description": "Sample Description for version",
            "displayName": "Friendly name for package",
            "dist": {
                "integrity": "sha512-{sha512 signature of .tar.gz}",
                "shasum": "{sha1 of .tar.gz}",
                "tarball": "https://example.com/example.tar.gz"
            },
            "name": "com.example.package",
            "version": "0.0.1",
            "_id": "[email protected]"
        }
    },
    "_attachments": {},
    "_id": "com.example.package",
    "_rev": ""
}

As you can see, there’s quite a bit of extra metadata here that UPM needs outside of the base NPM package metadata format. Namely _id, _attachments and _rev. I’m not 100% sure as to why Unity requires these to be present, but luckily, only _id appears to matter.


Conclusion

Overall, this was a pretty fun project, didn’t take a lot of time, and costs me barely anything due to it all running in a Serverless Lambda container which only charges me for execution time.

The bulk of the execution time currently goes towards the initial GET request to VRChat’s own package registry server and the GET to DynamoDB to retrieve the file shasums. These could both be improved with caching, which I intend to do in the near future. I currently cache the responses using CloudFlare for 2 hours per-client which decreased the invocation count even more.

So far, my analytics are reporting around 300 unique users a day utilizing the endpoint, with only a handful of requests throwing internal server errors, which I later discovered was due to people visiting the link in browsers and requesting favicons and have since fixed.