npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@xboxreplay/express-ugc-proxy

v0.4.3

Published

Express middleware to proxy user-generated content to your own host and embed them on social media platforms such as Facebook, Twitter, Discord, etc.

Downloads

17

Readme

Express UGC Proxy

Express middleware to proxy user-generated content to your own host and embed them on social media platforms such as Facebook, Twitter, Discord, etc.

Installation

npm install @xboxreplay/express-ugc-proxy

But, why?

Each user-generated content has an unique URI which is only valid for a few hours. If this URI is used and shared (via direct link or fetched by an external platform thanks to the meta tags) it may be cached and will become unreachable once expired. Or worse, blocked by default for security reasons (CORS policies).

The idea behind this proxy is to create an unique URI for each content and handle all the fetch, reload and even cache logic inside it.

Demo

A live demo is available here.

Examples

Important notice: This proxy is inspired by the one used on XboxReplay.net. The behavior is a bit different but performances are much better (for reasons).

  • Gameclip: https://www.xboxreplay.net/ugc-files/clips/2535465515082324/d1adc8aa-0a31-4407-90f2-7e9b54b0347c/388f97c0-17bc-4939-a592-d43c365acc48/gameclip.mp4
  • Gameclip thumbnail (Small): https://www.xboxreplay.net/ugc-files/clips/2535465515082324/d1adc8aa-0a31-4407-90f2-7e9b54b0347c/388f97c0-17bc-4939-a592-d43c365acc48/thumbnail-small.png
  • Screenshot (Large): https://www.xboxreplay.net/ugc-files/screenshots/2535465515082324/d1adc8aa-0a31-4407-90f2-7e9b54b0347c/1f002a0d-6100-4976-b5c6-e3580b6bc061/screenshot.png

Usage example

import express from 'express';
import UGCMiddleware from '@xboxreplay/express-ugc-proxy';
import XboxLiveAuth from '@xboxreplay/xboxlive-auth';

const app = express();

app.use('/ugc-files, UGCMiddleware.handle(
    XboxLiveAuth.authenticate('[email protected]', '*********')
), { debug: true });

app.listen(8888, err => {
    if (err) console.error(err);
    else console.info('> Listening at http://127.0.0.1:8888');
});

Then navigate to http://127.0.0.1:8888/ugc-files/gameclips/2535465515082324/d1adc8aa-0a31-4407-90f2-7e9b54b0347c/388f97c0-17bc-4939-a592-d43c365acc48/gameclip.mp4

Path composition

┌─────────────────────────────────────────────────────────────────────────┐
│      type     │     XUID     │     SCID     │     ID     │     name     │
└─────────────────────────────────────────────────────────────────────────┘
  • Supported types: gameclips | screenshots (may be overridden, see options)
  • Supported names ("gameclips" only): gameclip.mp4
  • Supported names ("screenshots" only): screenshot.png
  • Supported names (common): thumbnail-small.png | thumbnail-large.png

Parameters

  • XBLAuthenticateMethod {Promise<{ XSTSToken: string, userHash: string }>} See below
  • options {Object?}
    • cache {Object?} See below
    • onRequestError {Function?} See below
    • fileTypesMapping {Object?} Used to override default file types
      • gameclips? {string?}
      • screenshots? {string}
        • Example: { gameclips: 'clips' } Gameclips will be served from /clips/... instead of /gameclips/...
    • debug {boolean?} Stdout the error and display its reason in response body
    • redirectOnFetch {boolean?} Skip the proxy phase and redirect to the media URI
options.XBLAuthenticateMethod

This method must returns a Promise with a valid XSTSToken and an userHash which are used by the @xboxreplay/xboxlive-api module to fetch the targeted file. To prevent an authentication at each request wrap the authenticate method exposed by the @xboxreplay/xboxlive-auth module and its response inside a Promise and store / return its response as long it's valid.

Of course an in-memory data structure store as redis is recommended for this kind of usage.

let XBLAuthorization = null;

const XBLAuthenticateMethod = () =>
    new Promise((resolve, reject) => {
        if (XBLAuthorization !== null) {
            const hasExpired =
                XBLAuthorization.expiresOn !== null &&
                new Date(XBLAuthorization.expiresOn) <= new Date();

            if (hasExpired === false) {
                return resolve({
                    XSTSToken: XBLAuthorization.XSTSToken,
                    userHash: XBLAuthorization.userHash
                });
            }
        }

        return XboxLiveAuth.authenticate('[email protected]', '*********')
            .then(({ XSTSToken, userHash, expiresOn }) => {
                XBLAuthorization = { XSTSToken, userHash, expiresOn };
                return resolve({
                    XSTSToken: XBLAuthorization.XSTSToken,
                    userHash: XBLAuthorization.userHash
                });
            })
            .catch(reject);
    });

app.use('/ugc-files, UGCMiddleware.handle(
    XBLAuthenticateMethod
));
options.onRequestError

By default if something goes wrong the request will be closed and a HTTP status code will be returned to the client, including the error reason if the debug mode is enabled. A custom behavior is possible with this option.

const onRequestError = (details, res, next) => {
    const { statusCode, reason } = details;
    return res.redirect(`/my-error-page?code=${statusCode}&reason=${reason}`);
};

app.use('/ugc-files, UGCMiddleware.handle(
    XBLAuthenticateMethod
), { onRequestError });
options.cache

Once retrieved each file URI has a lifetime of approxymatively 1 hour. To prevent useless API calls during this period specify a getter and a setter method and let all the logic behind to be handled by the middleware itself.

// Example with redis
const redis = require('redis');
const client = redis.createClient();

const cache = {
    keySeparator: ':',
    forceUppercase: false,
    getter: client.get(key, cb),
    setter: client.set(key, payload, cb)
};

app.use('/ugc-files, UGCMiddleware.handle(
    XBLAuthenticateMethod
), { cache });
  • Available options:
    • keySeparator {string?} Default: ":"
    • forceUppercase {boolean?} Default: false
    • getter {Function}
    • setter {Function}

Proxy all the way?

As specified upper redirectOnFetch option allows you to skip the proxy phase and redirect to the media URI. This case is recommended if you want to stream a media directly from Azure servers on your own website to prevent useless memory usage.

app.use('/redirect-ugc-files, UGCMiddleware.handle(
    XBLAuthenticateMethod
), { redirectOnFetch: true });

app.use('/stream-ugc-files, UGCMiddleware.handle(
    XBLAuthenticateMethod
), { redirectOnFetch: false });

What's next?

  • Add tests 🤷
  • ~~Support caching~~
  • ~~Allow custom file types mapping~~