|
| 1 | +--- |
| 2 | +title: "Tutorial: Integrate multiple Immersive Reader resources" |
| 3 | +titleSuffix: Azure Cognitive Services |
| 4 | +description: In this tutorial, you'll create a Node.js application that launches the Immersive Reader using multiple Immersive Reader resources. |
| 5 | +author: skamal |
| 6 | +manager: nitinme |
| 7 | + |
| 8 | +ms.service: cognitive-services |
| 9 | +ms.subservice: immersive-reader |
| 10 | +ms.topic: tutorial |
| 11 | +ms.date: 01/14/2020 |
| 12 | +ms.author: skamal |
| 13 | +#Customer intent: As a developer, I want to learn more about the Immersive Reader SDK so that I can fully utilize all that the SDK has to offer. |
| 14 | +--- |
| 15 | + |
| 16 | +# Tutorial: Integrate multiple Immersive Reader resources |
| 17 | + |
| 18 | +In the [overview](./overview.md), you learned about what the Immersive Reader is and how it implements proven techniques to improve reading comprehension for language learners, emerging readers, and students with learning differences. In the [Node.js quickstart](./quickstart-nodejs.md), you learned how to use Immersive Reader with a single resource. This tutorial covers how to integrate multiple Immersive Reader resources in the same application. In this tutorial, you learn how to: |
| 19 | + |
| 20 | +> [!div class="checklist"] |
| 21 | +> * Create multiple Immersive Reader resource under an existing resource group |
| 22 | +> * Launch the Immersive Reader using multiple resources |
| 23 | +
|
| 24 | +If you don't have an Azure subscription, create a [free account](https://azure.microsoft.com/free/?WT.mc_id=A261C142F) before you begin. |
| 25 | + |
| 26 | +## Prerequisites |
| 27 | + |
| 28 | +* Follow the [quickstart](./quickstart-nodejs.md) to create a web app that launches the Immersive Reader with NodeJS. In that quickstart, you configure a single Immersive Reader resource. We will build on top of that in this tutorial. |
| 29 | + |
| 30 | +## Create the Immersive Reader resources |
| 31 | + |
| 32 | +Follow [these instructions](./how-to-create-immersive-reader.md) to create each Immersive Reader resource. The **Create-ImmersiveReaderResource** script has `ResourceName`, `ResourceSubdomain`, and `ResourceLocation` as parameters. These should be unique for each resource being created. The remaining parameters should be the same as what you used when setting up your first Immersive Reader resource. This way, each resource can be linked to the same Azure resource group and Azure AD application. |
| 33 | + |
| 34 | +The example below shows how to create two resources, one in WestUS, and another in EastUS. Notice the unique values for `ResourceName`, `ResourceSubdomain`, and `ResourceLocation`. |
| 35 | + |
| 36 | +```azurepowershell-interactive |
| 37 | +Create-ImmersiveReaderResource |
| 38 | + -SubscriptionName <SUBSCRIPTION_NAME> ` |
| 39 | + -ResourceName Resource_name_wus ` |
| 40 | + -ResourceSubdomain resource-subdomain-wus ` |
| 41 | + -ResourceSKU <RESOURCE_SKU> ` |
| 42 | + -ResourceLocation westus ` |
| 43 | + -ResourceGroupName <RESOURCE_GROUP_NAME> ` |
| 44 | + -ResourceGroupLocation <RESOURCE_GROUP_LOCATION> ` |
| 45 | + -AADAppDisplayName <AAD_APP_DISPLAY_NAME> ` |
| 46 | + -AADAppIdentifierUri <AAD_APP_IDENTIFIER_URI> ` |
| 47 | + -AADAppClientSecret <AAD_APP_CLIENT_SECRET> |
| 48 | +
|
| 49 | +Create-ImmersiveReaderResource |
| 50 | + -SubscriptionName <SUBSCRIPTION_NAME> ` |
| 51 | + -ResourceName Resource_name_eus ` |
| 52 | + -ResourceSubdomain resource-subdomain-eus ` |
| 53 | + -ResourceSKU <RESOURCE_SKU> ` |
| 54 | + -ResourceLocation eastus ` |
| 55 | + -ResourceGroupName <RESOURCE_GROUP_NAME> ` |
| 56 | + -ResourceGroupLocation <RESOURCE_GROUP_LOCATION> ` |
| 57 | + -AADAppDisplayName <AAD_APP_DISPLAY_NAME> ` |
| 58 | + -AADAppIdentifierUri <AAD_APP_IDENTIFIER_URI> ` |
| 59 | + -AADAppClientSecret <AAD_APP_CLIENT_SECRET> |
| 60 | +``` |
| 61 | + |
| 62 | +## Add resources to environment configuration |
| 63 | + |
| 64 | +In the quickstart, you created an environment configuration file that contains the `TenantId`, `ClientId`, `ClientSecret`, and `Subdomain` parameters. Since all of your resources use the same Azure AD application, we can use the same values for the `TenantId`, `ClientId`, and `ClientSecret`. The only change that needs to be made is to list each subdomain for each resource. |
| 65 | + |
| 66 | +Your new __.env__ file should now look something like the following: |
| 67 | + |
| 68 | +```text |
| 69 | +TENANT_ID={YOUR_TENANT_ID} |
| 70 | +CLIENT_ID={YOUR_CLIENT_ID} |
| 71 | +CLIENT_SECRET={YOUR_CLIENT_SECRET} |
| 72 | +SUBDOMAIN_WUS={YOUR_WESTUS_SUBDOMAIN} |
| 73 | +SUBDOMAIN_EUS={YOUR_EASTUS_SUBDOMAIN} |
| 74 | +``` |
| 75 | + |
| 76 | +Be sure not to commit this file into source control, as it contains secrets that should not be made public. |
| 77 | + |
| 78 | +Next, we're going to modify the _routes\index.js_ file that we created to support our multiple resources. Replace its content with the following code. |
| 79 | + |
| 80 | +As before, this code creates an API endpoint that acquires an Azure AD authentication token using your service principal password. This time, it allows the user to specify a resource location and pass it in as a query parameter. It then returns an object containing the token and the corresponding subdomain. |
| 81 | + |
| 82 | +```javascript |
| 83 | +var express = require('express'); |
| 84 | +var router = express.Router(); |
| 85 | +var request = require('request'); |
| 86 | + |
| 87 | +/* GET home page. */ |
| 88 | +router.get('/', function(req, res, next) { |
| 89 | + res.render('index', { title: 'Express' }); |
| 90 | +}); |
| 91 | + |
| 92 | +router.get('/GetTokenAndSubdomain', function(req, res) { |
| 93 | + try { |
| 94 | + request.post({ |
| 95 | + headers: { |
| 96 | + 'content-type': 'application/x-www-form-urlencoded' |
| 97 | + }, |
| 98 | + url: `https://login.windows.net/${process.env.TENANT_ID}/oauth2/token`, |
| 99 | + form: { |
| 100 | + grant_type: 'client_credentials', |
| 101 | + client_id: process.env.CLIENT_ID, |
| 102 | + client_secret: process.env.CLIENT_SECRET, |
| 103 | + resource: 'https://cognitiveservices.azure.com/' |
| 104 | + } |
| 105 | + }, |
| 106 | + function(err, resp, tokenResult) { |
| 107 | + if (err) { |
| 108 | + console.log(err); |
| 109 | + return res.status(500).send('CogSvcs IssueToken error'); |
| 110 | + } |
| 111 | + |
| 112 | + var tokenResultParsed = JSON.parse(tokenResult); |
| 113 | + |
| 114 | + if (tokenResultParsed.error) { |
| 115 | + console.log(tokenResult); |
| 116 | + return res.send({error : "Unable to acquire Azure AD token. Check the debugger for more information."}) |
| 117 | + } |
| 118 | + |
| 119 | + var token = tokenResultParsed.access_token; |
| 120 | + |
| 121 | + var subdomain = ""; |
| 122 | + var region = req.query && req.query.region; |
| 123 | + switch (region) { |
| 124 | + case "eus": |
| 125 | + subdomain = process.env.SUBDOMAIN_EUS |
| 126 | + break; |
| 127 | + case "wus": |
| 128 | + default: |
| 129 | + subdomain = process.env.SUBDOMAIN_WUS |
| 130 | + } |
| 131 | + |
| 132 | + return res.send({token, subdomain}); |
| 133 | + }); |
| 134 | + } catch (err) { |
| 135 | + console.log(err); |
| 136 | + return res.status(500).send('CogSvcs IssueToken error'); |
| 137 | + } |
| 138 | +}); |
| 139 | + |
| 140 | +module.exports = router; |
| 141 | +``` |
| 142 | + |
| 143 | +The **getimmersivereaderlaunchparams** API endpoint should be secured behind some form of authentication (for example, [OAuth](https://oauth.net/2/)) to prevent unauthorized users from obtaining tokens to use against your Immersive Reader service and billing; that work is beyond the scope of this tutorial. |
| 144 | + |
| 145 | +## Launch the Immersive Reader with sample content |
| 146 | + |
| 147 | +1. Open _views\index.pug_, and replace its content with the following code. This code populates the page with some sample content, and adds two buttons that launches the Immersive Reader. One for launching Immersive Reader for the EastUS resource, and another for the WestUS resource. |
| 148 | + |
| 149 | + ```pug |
| 150 | + doctype html |
| 151 | + html |
| 152 | + head |
| 153 | + title Immersive Reader Quickstart Node.js |
| 154 | +
|
| 155 | + link(rel='stylesheet', href='https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css') |
| 156 | +
|
| 157 | + // A polyfill for Promise is needed for IE11 support. |
| 158 | + script(src='https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js') |
| 159 | +
|
| 160 | + script(src='https://contentstorage.onenote.office.net/onenoteltir/immersivereadersdk/immersive-reader-sdk.1.0.0.js') |
| 161 | + script(src='https://code.jquery.com/jquery-3.3.1.min.js') |
| 162 | +
|
| 163 | + style(type="text/css"). |
| 164 | + .immersive-reader-button { |
| 165 | + background-color: white; |
| 166 | + margin-top: 5px; |
| 167 | + border: 1px solid black; |
| 168 | + float: right; |
| 169 | + } |
| 170 | + body |
| 171 | + div(class="container") |
| 172 | + button(class="immersive-reader-button" data-button-style="icon" data-locale="en" onclick='handleLaunchImmersiveReader("wus")') WestUS Immersive Reader |
| 173 | + button(class="immersive-reader-button" data-button-style="icon" data-locale="en" onclick='handleLaunchImmersiveReader("eus")') EastUS Immersive Reader |
| 174 | +
|
| 175 | + h1(id="ir-title") About Immersive Reader |
| 176 | + div(id="ir-content" lang="en-us") |
| 177 | + p Immersive Reader is a tool that implements proven techniques to improve reading comprehension for emerging readers, language learners, and people with learning differences. The Immersive Reader is designed to make reading more accessible for everyone. The Immersive Reader |
| 178 | +
|
| 179 | + ul |
| 180 | + li Shows content in a minimal reading view |
| 181 | + li Displays pictures of commonly used words |
| 182 | + li Highlights nouns, verbs, adjectives, and adverbs |
| 183 | + li Reads your content out loud to you |
| 184 | + li Translates your content into another language |
| 185 | + li Breaks down words into syllables |
| 186 | +
|
| 187 | + h3 The Immersive Reader is available in many languages. |
| 188 | +
|
| 189 | + p(lang="es-es") El Lector inmersivo está disponible en varios idiomas. |
| 190 | + p(lang="zh-cn") 沉浸式阅读器支持许多语言 |
| 191 | + p(lang="de-de") Der plastische Reader ist in vielen Sprachen verfügbar. |
| 192 | + p(lang="ar-eg" dir="rtl" style="text-align:right") يتوفر \"القارئ الشامل\" في العديد من اللغات. |
| 193 | +
|
| 194 | + script(type="text/javascript"). |
| 195 | + function getTokenAndSubdomainAsync(region) { |
| 196 | + return new Promise(function (resolve, reject) { |
| 197 | + $.ajax({ |
| 198 | + url: "/GetTokenAndSubdomain", |
| 199 | + type: "GET", |
| 200 | + data: { |
| 201 | + region: region |
| 202 | + }, |
| 203 | + success: function (data) { |
| 204 | + if (data.error) { |
| 205 | + reject(data.error); |
| 206 | + } else { |
| 207 | + resolve(data); |
| 208 | + } |
| 209 | + }, |
| 210 | + error: function (err) { |
| 211 | + reject(err); |
| 212 | + } |
| 213 | + }); |
| 214 | + }); |
| 215 | + } |
| 216 | +
|
| 217 | + function handleLaunchImmersiveReader(region) { |
| 218 | + getTokenAndSubdomainAsync(region) |
| 219 | + .then(function (response) { |
| 220 | + const token = response["token"]; |
| 221 | + const subdomain = response["subdomain"]; |
| 222 | + // Learn more about chunk usage and supported MIME types https://docs.microsoft.com/azure/cognitive-services/immersive-reader/reference#chunk |
| 223 | + const data = { |
| 224 | + title: $("#ir-title").text(), |
| 225 | + chunks: [{ |
| 226 | + content: $("#ir-content").html(), |
| 227 | + mimeType: "text/html" |
| 228 | + }] |
| 229 | + }; |
| 230 | + // Learn more about options https://docs.microsoft.com/azure/cognitive-services/immersive-reader/reference#options |
| 231 | + const options = { |
| 232 | + "onExit": exitCallback, |
| 233 | + "uiZIndex": 2000 |
| 234 | + }; |
| 235 | + ImmersiveReader.launchAsync(token, subdomain, data, options) |
| 236 | + .catch(function (error) { |
| 237 | + alert("Error in launching the Immersive Reader. Check the console."); |
| 238 | + console.log(error); |
| 239 | + }); |
| 240 | + }) |
| 241 | + .catch(function (error) { |
| 242 | + alert("Error in getting the Immersive Reader token and subdomain. Check the console."); |
| 243 | + console.log(error); |
| 244 | + }); |
| 245 | + } |
| 246 | +
|
| 247 | + function exitCallback() { |
| 248 | + console.log("This is the callback function. It is executed when the Immersive Reader closes."); |
| 249 | + } |
| 250 | + ``` |
| 251 | +
|
| 252 | +3. Our web app is now ready. Start the app by running: |
| 253 | +
|
| 254 | + ```bash |
| 255 | + npm start |
| 256 | + ``` |
| 257 | +
|
| 258 | +4. Open your browser and navigate to [http://localhost:3000](http://localhost:3000). You should see the above content on the page. Click on either the **EastUS Immersive Reader** button or the **WestUS Immersive Reader** button to launch the Immersive Reader using those respective resources. |
| 259 | +
|
| 260 | +## Next steps |
| 261 | +
|
| 262 | +* Explore the [Immersive Reader SDK](https://github.com/microsoft/immersive-reader-sdk) and the [Immersive Reader SDK Reference](./reference.md) |
| 263 | +* View code samples on [GitHub](https://github.com/microsoft/immersive-reader-sdk/tree/master/js/samples/advanced-csharp) |
0 commit comments