Retailer Integration

Introduction

The Eluvio Content Fabric provides a next gen decentralized, high performance and tamper proof content delivery network for publisher-to-fan content entitlement and distribution. The Media Wallet app provides a secure personal vault through which end users can access and enjoy their entitled content on browser and connected TV. Exclusively owned Media Bundles can be minted, owned, and distributed on the Content Fabric for authorized Media Wallet holders.

Authorization is enabled through ownership of a bundle Access Pass minted to the user’s wallet address on the Fabric’s blockchain. Together the Fabric and the Media Wallet allow publishers to distribute and authorize end users to any video, audio, interactive or static content experience and update these over time.

More Info

Overview

This document covers how Retailers selling Media Bundles can use the APIs of the Media Wallet and Content Fabric to integrate the Bundle experience directly into any Retail entertainment site (web or Connected TV) via “deep linking”. APIs cover signing the user on to their Media Wallet using a 3rd party sign on, activating the bundle via minting the corresponding owned entitlement on the Content Fabric to their wallet address, and opening the bundle through a direct link (deep link). Example code is provided for browser (mobile and desktop) via Javascript and for for Apple TV (TVOS). Additional samples can be provided for any Connected TV platform the Media Wallet runs on.

Application Flow

The flow to entitle the user to the Media Bundle or to open an entitled Media Bundle assumes we start within the Retailer Application experience. The flow assumes the user is signed on to the Retailer Application and the application has a valid JWT OpenID Bearer token in scope, the “Client Access Token” (CAT). The Retailer Application then uses an API signing key to create a Signed Entitlement that it will then pass to the Media Wallet API along with the CAT to mint the Media Bundle entitlement to the user’s wallet and open the bundle, or directly open the item in the user’s wallet if the user wallet already owns the bundle Access Pass.

The Signed Entitlement takes as arguments the Retailer’s Tenant ID on the Content Fabric, the Content Object ID of the Marketplace object in the tenancy, and the SKU of the Bundle item which is linked to the Marketplace object. The signing key can be obtained from the Tenant configuration.

Alternatively, if the Retailer app does not want to use its native/3rd party sign on, the Retailer App can call the Media Wallet login API directly and pass the returned Fabric client signed Access Token (CSAT) or pass no sign on token at all, in which case the Media Wallet will prompt the user to sign in before proceeding to mint or open bundles. Example applications

Web Retailer App

Eluvio has provided a sample application and source code for a Retailer Application backend and a client side Javascript code for integration in web applications that illustrates this flow.

Source code is available here:

https://github.com/eluv-io/elv-sample-custom-app-purchase

Test the Sample Application

You can test the application at https://appsvc.svc.eluv.io/sample-purchase/

Key points for the sample are as follows:

Configuration

The sample application is configured to mint a test Eluvio Media Bundle containing a video and an image item using the following test tenancy, marketplace, and SKU:

  // Sample configuration: "eluvio test media bundle"
  let tenant = "iten4T34qbBbm6qEWMqnmqrkPZ38MVB8";
  let marketplace = "iq__D3N77Nw1ATwZvasBNnjAQWeVLWV";
  let sku = "5MmuT4t6RoJtrT9h1yTnos";

Entitlement Generation (backend)

Note that the primary function that generates the entitlement to be passed to the Media Wallet takes in private key. This is an API signing key associated with the Tenancy, and should be secured by the Retailer Application appropriately. The function also takes a purchaseId, which can be any id string unique to the bundle purchase, such as a purchase ID in the retailer’s system, and is used to associate a bundle with a unique purchase. A new Bundle Access Pass is only minted for the target wallet if the id passed is unique.

  /**
   * Generate a mint entitlement
   *
   * @param {string} tenant - tenant ID in 'iten' format
   * @param {string} marketplace - marketplace object ID in 'iq__' format
   * @param {string} sku - SKU of the item
   * @param {number} amount - number of items of that SKU
   * @param {string} user - user ID in any format, usually the 'sub' of the id/access token; an email, username, address, etc.
   * @returns {Promise<Object>} - the entitlement JSON and signature
   */
  const Entitlement = async({tenant, marketplace, sku, amount, user, purchaseId}) => {
    const message = {
      tenant_id: tenant,
      marketplace_id: marketplace,
      items: [ { sku: sku, amount: amount } ],
      user: user,
      purchase_id: purchaseId,
    };
    const sig = await CreateSignedMessageJSON({client, obj: message});

    return { entitlementJson: message, signature: sig };
  };

  const CreateSignedMessageJSON = async ({
    client,
    obj,
  }) => {
    const type = "mje_" // JSON message, EIP192 signature
    const msg = JSON.stringify(obj);

    const signature = await client.PersonalSign({message: msg, addEthereumPrefix: true});
    return `${type}${Utils.B58(
      Buffer.concat([
          Buffer.from(signature.replace(/^0x/, ""), "hex"),
          Buffer.from(msg)
      ])
    )}`;
  }

  async function GenerateEntitlement(tenant, marketplace, sku, user, purchaseId) {
    try {
      // Initialize client using environment variable PRIVATE_KEY
      client = await ElvClient.FromNetworkName({networkName: "main"});
      let wallet = client.GenerateWallet();
      let signer = wallet.AddAccount({
        privateKey: process.env.PRIVATE_KEY
      });
      client.SetSigner({signer});
      client.ToggleLogging(false);
      console.log("SIGNER", client.CurrentAccountAddress());

      const { entitlementJson, signature } =
        await Entitlement({tenant, marketplace, sku, amount, user, purchaseId});
      console.log("ENTITLEMENT", entitlementJson);
      console.log("ENTITLEMENT_SIGNATURE", signature);

      return {entitlementJson, signature};
    } catch (e) {
      console.error("ERROR:", e);
      return {};
    }
  }

Frontend

The frontend sample application code is at https://github.com/eluv-io/elv-sample-custom-app-purchase/blob/main/index.js

It performs an OpenID sign on to an example 3rd party sign on provider https://frosty-sanderson-jl1xbi88ik.projects.oryapis.com/oauth2/token

And POSTs a request to generate an entitlement

  app.post('/gen-entitlement', async (req, res) => {
    console.log("gen-entitlement", req.body)
    let { tenant_id, marketplace_id, sku, purchase_id } = req.body;

    let user;
    const authToken = req.headers.authorization;
    if (authToken) {
      try {
        const tok = authToken.split(' ')[1];
        const client = await ElvClient.FromNetworkName({networkName: networkName});
        user = client.utils.DecodeSignedToken(tok)?.payload?.adr;
      } catch (e) {
        console.error("Error decoding token", e);
        res.send({"error": "Error decoding token"});
        return;
      }
    } else {
      res.send({ "error": "No token" });
      return;
    }

    const e = await GenerateEntitlement(tenant_id, marketplace_id, sku, user, purchase_id);
    entitlementJson = e.entitlementJson;
    signature = e.signature;

    res.send({ "entitlement": entitlementJson, signature });
  });

And finally submits the request to the media wallet (which mints if needed, and redirects to the Bundle drill down):

    app.get('/goToWallet', async (req, res) => {
        const decode = await DecodeSignedMessageJSON({signedMessage: signature});
        decode?.obj && console.log("EntitlementClaim obj: " + JSON.stringify(decode.obj));
        const {tenant_id, marketplace_id, items, user, purchase_id} = decode?.obj;
        const sku = items[0].sku;

        const authInfo = {
            idToken: idToken,
            signerURIs: "https://wlt.stg.svc.eluv.io",
            user: { email: user }
        };
        const b64 = Buffer.from(JSON.stringify(authInfo)).toString('base64');
        const redirect = walletUrl + "/marketplace/" + marketplace_id + "/store/" + sku + "/claim/" + signature + "?auth=" + b64
        res.redirect(redirect);
    });

TVOS Retailer App

Retailer TV applications can equally embed the Media Bundle experience via the Media Wallet deep link APIs. Eluvio has provided a sample application for TVOS in Swift that embeds the same test Media Bundle. The sample app requires a beta version of the Eluvio Media Wallet (distributed only on TestFlight), Eluvio Media Wallet 1.0.6 build (154) or greater. To request test access please contact support@eluv.io.

Source code is available here:

https://github.com/eluv-io/tvos-deeplink-samples

Key points for the sample are as follows:

Configuration

The sample is set up for the test tenant, marketplace and bundle SKU. To change these values, see https://github.com/eluv-io/tvos-deeplink-samples/blob/main/EluvioDeepLinkSample/EluvioDeepLinkSample/ContentView.swift

  let TENANT_ID = "iten34Y7Tzso2mRqhzZ6yJDZs2Sqf8L"
  let MARKETPLACE = "iq__D3N77Nw1ATwZvasBNnjAQWeVLWV"
  let SKU = "5MmuT4t6RoJtrT9h1yTnos"

3rd Party Sign on Provider

To use your own 3rd party login provider, you will change the provider URL in this function.

  func CreateLoginUrl(marketplaceId: String) -> String {
    return "https://wallet.contentfabric.io/login?mid=\(marketplaceId)&useOry=true&action=login&mode=login&response=code&source=code"
  }

Entitlement Generation

This sample uses the same Entitlement Generation (backend) as the Web example shown above.

The Eluvio Media Wallet has defined a deep link scheme elvwallet:// when installed

  • To link to a specific SKU:

    elvwallet://items?contract={contract_address}&marketplace={marketplace_id}&sku={sku}&back_link=(backlink)

  • To link to play a video from the content fabric:

    elvwallet://play?contract={contract_address}

  • To link to mint a bundle with entitlements:

    elvwallet://mint?marketplace={marketplace_id}&sku={sku}&entitlement={entitlement}&back_link=(backlink)

see Linker.swift