Wrap JSON response with dynamic unique callback using Edge Workers

This example demonstrates how an EdgeWorker can be used to wrap JSON response with dynamic unique callback function leveraging Response Provider and Stream API for efficient content transformation. The EW should be enabled on JSON requests containing callback query parameter. This can easily be accomplished with match on file extension and query parameter match in Property Manager. When such request comes in, this EW removes the callback query param and makes a sub-request to fetch the JSON data, serving it as a stream. EW code adds prefix with callback function name captured from the query param and suffix. Both JSON data and transformed data can be cached and this can be achieved with standard "Caching" behavior in Property Manager (if caching is allowed in your use-case).

Link to GitHub

Below is the code snippet in main.js

Outcome : Add callback as ?callback=someFunctionName


/*
(c) Copyright 2020 Akamai Technologies, Inc. Licensed under Apache 2 license.
Version: 0.1
Purpose: Wrap JSON response with dynamic unique callback function.
Repo: https://github.com/akamai/edgeworkers-examples/tree/master/edgecompute/examples/stream/jsonp-wrapper
*/

import { TransformStream } from "streams";
import { httpRequest } from "http-request";
import { createResponse } from "create-response";
import URLSearchParams from "url-search-params";

function str2uint8arr(s) {
    return Uint8Array.from(Array.from(s, (x) => x.charCodeAt(0)))
}

export function responseProvider(request) {
    let params = new URLSearchParams(request.query);
    let callbackName = params.get("callback");
    params.delete("callback");

    const jsonpTransformer = new TransformStream({
        transform(chunk, controller) {
            if (chunk) {
                controller.enqueue(chunk);
            }
        },
        start(controller) {
            controller.enqueue(str2uint8arr(callbackName + "("));
        },
        flush(controller) {
            controller.enqueue(str2uint8arr(")"));
        }
    });

    const options = { 'headers': {'Accept': 'application/json'} };
    return httpRequest(`${request.scheme}://${request.host}${request.path}?${params.toString()}`, options).then((response) => {

        // Get headers from response
        let headers = response.getHeaders();
        // Remove content-encoding header.  The response stream from EdgeWorkers is not encoded.
        // If original response contains `Content-Encoding: gzip`, then the Content-Encoding header does not match the actual encoding.
        delete headers["content-encoding"];
        // Remove `Content-Length` header.  Modifying JSON is likely to change the content length.
        // Leaving the Length of the original content would be incorrect.
        delete headers["content-length"];

        return createResponse(
          response.status,
          headers,
          response.body.pipeThrough(jsonpTransformer)
        );
    });
}