0.4.3 • Published 5 years ago

@xboxreplay/express-ugc-proxy v0.4.3

Weekly downloads
1
License
MIT
Repository
github
Last release
5 years ago

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).

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('xbl-account@domain.com', '*********')
), { 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('xbl-account@domain.com', '*********')
            .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
0.4.3

5 years ago

0.4.2

5 years ago

0.4.1

5 years ago

0.4.0

5 years ago

0.3.0

5 years ago

0.2.1

5 years ago

0.2.0

5 years ago

0.1.4

5 years ago

0.1.3

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago