1
/*
2
 * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5
 * the License. A copy of the License is located at
6
 *
7
 *     http://aws.amazon.com/apache2.0/
8
 *
9
 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
11
 * and limitations under the License.
12
 */
13 1
import {
14
	ConsoleLogger as Logger,
15
	Signer,
16
	Credentials,
17
	Constants,
18
} from '@aws-amplify/core';
19

20 1
import { AbstractXRProvider } from './XRProvider';
21
import { ProviderOptions, SceneOptions } from '../types';
22 1
import {
23
	XRNoSceneConfiguredError,
24
	XRSceneNotFoundError,
25
	XRSceneNotLoadedError,
26
	XRNoDomElement,
27
	XRSceneLoadFailure,
28
} from '../Errors';
29

30
type SumerianSceneOptions = SceneOptions & { progressCallback: Function };
31

32 1
const SUMERIAN_SERVICE_NAME = 'sumerian';
33

34 1
const logger = new Logger('SumerianProvider');
35

36 1
export class SumerianProvider extends AbstractXRProvider {
37 1
	constructor(options: ProviderOptions = {}) {
38 1
		super(options);
39
	}
40

41 1
	getProviderName() {
42 1
		return 'SumerianProvider';
43
	}
44

45 1
	private async loadScript(url) {
46 0
		return new Promise((resolve, reject) => {
47 0
			const scriptElement = document.createElement('script');
48 0
			scriptElement.src = url;
49

50 0
			scriptElement.addEventListener('load', event => {
51 0
				resolve();
52
			});
53

54 0
			scriptElement.addEventListener('error', event => {
55 0
				reject(new Error(`Failed to load script: ${url}`));
56
			});
57

58 0
			document.head.appendChild(scriptElement);
59
		});
60
	}
61

62 1
	public async loadScene(
63
		sceneName: string,
64
		domElementId: string,
65
		sceneOptions: SumerianSceneOptions
66
	) {
67 1
		if (!sceneName) {
68 1
			const errorMsg = 'No scene name passed into loadScene';
69 1
			logger.error(errorMsg);
70 1
			throw new XRSceneLoadFailure(errorMsg);
71
		}
72

73 1
		if (!domElementId) {
74 0
			const errorMsg = 'No dom element id passed into loadScene';
75 0
			logger.error(errorMsg);
76 0
			throw new XRNoDomElement(errorMsg);
77
		}
78

79 1
		const element = document.getElementById(domElementId);
80 1
		if (!element) {
81 1
			const errorMsg = `DOM element id, ${domElementId} not found`;
82 1
			logger.error(errorMsg);
83 1
			throw new XRNoDomElement(errorMsg);
84
		}
85

86 1
		const scene = this.getScene(sceneName);
87 1
		if (!scene.sceneConfig) {
88 0
			const errorMsg = `No scene config configured for scene: ${sceneName}`;
89 0
			logger.error(errorMsg);
90 0
			throw new XRSceneLoadFailure(errorMsg);
91
		}
92

93 1
		const sceneUrl = scene.sceneConfig.url;
94 1
		const sceneId = scene.sceneConfig.sceneId;
95

96
		let sceneRegion;
97 1
		if (scene.sceneConfig.hasOwnProperty('region')) {
98
			// Use the scene region on the Sumerian scene configuration
99 0
			sceneRegion = scene.sceneConfig.region;
100 1
		} else if (this.options.hasOwnProperty('region')) {
101
			// Use the scene region on the XR category configuration
102 0
			sceneRegion = this.options.region;
103
		} else {
104 1
			const errorMsg = `No region configured for scene: ${sceneName}`;
105 1
			logger.error(errorMsg);
106 1
			throw new XRSceneLoadFailure(errorMsg);
107
		}
108

109 0
		const awsSDKConfigOverride = {
110
			region: sceneRegion,
111
			// This is passed to the AWS clients created in
112
			// Sumerian's AwsSystem
113
			// This helps other services(like Lex and Polly) to track
114
			// traffic coming from Sumerian scenes embedded with Amplify
115
			customUserAgent: `${Constants.userAgent}-SumerianScene`,
116
		};
117

118
		// We are signing the requests to Sumerian ourselves instead of using the AWS SDK
119
		// We want to set the user agent header
120 0
		const fetchOptions = {
121
			headers: {
122
				// This sets the AWS user agent string
123
				// So the Sumerian service knows this request is
124
				// from Amplify
125
				'X-Amz-User-Agent': Constants.userAgent,
126
			},
127
		};
128

129 0
		let url = sceneUrl;
130
		try {
131
			// Get credentials from Auth and sign the request
132 0
			const credentials = await Credentials.get();
133 0
			awsSDKConfigOverride['credentials'] = credentials;
134 0
			const accessInfo = {
135
				secret_key: credentials.secretAccessKey,
136
				access_key: credentials.accessKeyId,
137
				session_token: credentials.sessionToken,
138
			};
139

140 0
			const serviceInfo = {
141
				region: sceneRegion,
142
				service: SUMERIAN_SERVICE_NAME,
143
			};
144 0
			const request = Signer.sign(
145
				{ method: 'GET', url: sceneUrl },
146
				accessInfo,
147
				serviceInfo
148
			);
149 0
			fetchOptions.headers = { ...fetchOptions.headers, ...request.headers };
150 0
			url = request.url;
151
		} catch (e) {
152 0
			logger.debug('No credentials available, the request will be unsigned');
153
		}
154

155 1
		const apiResponse = await fetch(url, fetchOptions);
156 0
		const apiResponseJson = await apiResponse.json();
157 1
		if (apiResponse.status === 403) {
158 1
			if (apiResponseJson.message) {
159 0
				logger.error(
160
					`Failure to authenticate user: ${apiResponseJson.message}`
161
				);
162 0
				throw new XRSceneLoadFailure(
163
					`Failure to authenticate user: ${apiResponseJson.message}`
164
				);
165
			} else {
166 0
				logger.error(`Failure to authenticate user`);
167 0
				throw new XRSceneLoadFailure(`Failure to authenticate user`);
168
			}
169
		}
170

171
		// Get bundle data from scene api response
172 0
		const sceneBundleData = apiResponseJson.bundleData[sceneId];
173 0
		const sceneBundle = await fetch(sceneBundleData.url, {
174
			headers: sceneBundleData.headers,
175
		});
176 0
		const sceneBundleJson = await sceneBundle.json();
177

178
		try {
179
			// Load the Sumerian bootstrapper script into the DOM
180 0
			await this.loadScript(sceneBundleJson[sceneId].bootstrapperUrl);
181
		} catch (error) {
182 0
			logger.error(error);
183 0
			throw new XRSceneLoadFailure(error);
184
		}
185

186 0
		const progressCallback = sceneOptions.progressCallback
187 1
			? sceneOptions.progressCallback
188
			: undefined;
189 0
		const publishParamOverrides = scene.publishParamOverrides
190 1
			? scene.publishParamOverrides
191
			: undefined;
192

193 0
		const sceneLoadParams = {
194
			element,
195
			sceneId,
196
			sceneBundle: sceneBundleJson,
197
			apiResponse: apiResponseJson,
198
			progressCallback,
199
			publishParamOverrides,
200
			awsSDKConfigOverride,
201
		};
202

203
		// Load the scene into the dom and set the scene controller
204 0
		const sceneController = await (<any>window).SumerianBootstrapper.loadScene(
205
			sceneLoadParams
206
		);
207 0
		scene.sceneController = sceneController;
208 0
		scene.isLoaded = true;
209

210
		// Log scene warnings
211 0
		for (const warning of sceneController.sceneLoadWarnings) {
212 0
			logger.warn(`loadScene warning: ${warning}`);
213
		}
214
	}
215

216 1
	public isSceneLoaded(sceneName: string) {
217 1
		const scene = this.getScene(sceneName);
218 1
		return scene.isLoaded || false;
219
	}
220

221 1
	private getScene(sceneName: string) {
222 1
		if (!this.options.scenes) {
223 1
			const errorMsg = 'No scenes were defined in the configuration';
224 1
			logger.error(errorMsg);
225 1
			throw new XRNoSceneConfiguredError(errorMsg);
226
		}
227

228 1
		if (!sceneName) {
229 0
			const errorMsg = 'No scene name was passed';
230 0
			logger.error(errorMsg);
231 0
			throw new XRSceneNotFoundError(errorMsg);
232
		}
233

234 1
		if (!this.options.scenes[sceneName]) {
235 1
			const errorMsg = `Scene '${sceneName}' is not configured`;
236 1
			logger.error(errorMsg);
237 1
			throw new XRSceneNotFoundError(errorMsg);
238
		}
239

240 1
		return this.options.scenes[sceneName];
241
	}
242

243 1
	public getSceneController(sceneName: string) {
244 1
		if (!this.options.scenes) {
245 0
			const errorMsg = 'No scenes were defined in the configuration';
246 0
			logger.error(errorMsg);
247 0
			throw new XRNoSceneConfiguredError(errorMsg);
248
		}
249

250 1
		const scene = this.options.scenes[sceneName];
251 1
		if (!scene) {
252 0
			const errorMsg = `Scene '${sceneName}' is not configured`;
253 0
			logger.error(errorMsg);
254 0
			throw new XRSceneNotFoundError(errorMsg);
255
		}
256

257 1
		const sceneController = scene.sceneController;
258 1
		if (!sceneController) {
259 0
			const errorMsg = `Scene controller for '${sceneName}' has not been loaded`;
260 0
			logger.error(errorMsg);
261 0
			throw new XRSceneNotLoadedError(errorMsg);
262
		}
263

264 1
		return sceneController;
265
	}
266

267 1
	public isVRCapable(sceneName: string): boolean {
268 1
		const sceneController = this.getSceneController(sceneName);
269 1
		return sceneController.vrCapable;
270
	}
271

272 1
	public isVRPresentationActive(sceneName: string): boolean {
273 0
		const sceneController = this.getSceneController(sceneName);
274 0
		return sceneController.vrPresentationActive;
275
	}
276

277 1
	public start(sceneName: string) {
278 0
		const sceneController = this.getSceneController(sceneName);
279 0
		sceneController.start();
280
	}
281

282 1
	public enterVR(sceneName: string) {
283 0
		const sceneController = this.getSceneController(sceneName);
284 0
		sceneController.enterVR();
285
	}
286

287 1
	public exitVR(sceneName: string) {
288 0
		const sceneController = this.getSceneController(sceneName);
289 0
		sceneController.exitVR();
290
	}
291

292 1
	public isMuted(sceneName: string): boolean {
293 1
		const sceneController = this.getSceneController(sceneName);
294 1
		return sceneController.muted;
295
	}
296

297 1
	public setMuted(sceneName: string, muted: boolean) {
298 1
		const sceneController = this.getSceneController(sceneName);
299 1
		sceneController.muted = muted;
300
	}
301

302 1
	public onSceneEvent(
303
		sceneName: string,
304
		eventName: string,
305
		eventHandler: Function
306
	) {
307 0
		const sceneController = this.getSceneController(sceneName);
308 0
		sceneController.on(eventName, eventHandler);
309
	}
310

311 1
	public enableAudio(sceneName: string) {
312 0
		const sceneController = this.getSceneController(sceneName);
313 0
		sceneController.enableAudio();
314
	}
315 1
}

Read our documentation on viewing source code .

Loading