@reveldigital/player-client 0.0.15 → 0.0.16-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/karma.conf.js ADDED
@@ -0,0 +1,32 @@
1
+ // Karma configuration file, see link for more information
2
+ // https://karma-runner.github.io/1.0/config/configuration-file.html
3
+
4
+ module.exports = function (config) {
5
+ config.set({
6
+ basePath: '',
7
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
8
+ plugins: [
9
+ require('karma-jasmine'),
10
+ require('karma-chrome-launcher'),
11
+ require('karma-jasmine-html-reporter'),
12
+ require('karma-coverage-istanbul-reporter'),
13
+ require('@angular-devkit/build-angular/plugins/karma')
14
+ ],
15
+ client: {
16
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
17
+ },
18
+ coverageIstanbulReporter: {
19
+ dir: require('path').join(__dirname, '../../../coverage/reveldigital/player-client'),
20
+ reports: ['html', 'lcovonly', 'text-summary'],
21
+ fixWebpackSourcePaths: true
22
+ },
23
+ reporters: ['progress', 'kjhtml'],
24
+ port: 9876,
25
+ colors: true,
26
+ logLevel: config.LOG_INFO,
27
+ autoWatch: true,
28
+ browsers: ['Chrome'],
29
+ singleRun: false,
30
+ restartOnFileChange: true
31
+ });
32
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../../dist/reveldigital/player-client",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,39 +1,20 @@
1
- {
2
- "name": "@reveldigital/player-client",
3
- "version": "0.0.15",
4
- "description": "Helper library for interfacing with the Revel Digital player",
5
- "repository": {
6
- "url": "https://github.com/RevelDigital/reveldigital-client-library"
7
- },
8
- "author": {
9
- "name": "Mike Tinnes"
10
- },
11
- "license": "",
12
- "peerDependencies": {
13
- "@angular/common": "~13.3.9",
14
- "@angular/core": "~13.3.9"
15
- },
16
- "dependencies": {
17
- "tslib": "^2.4.1"
18
- },
19
- "module": "fesm2015/reveldigital-player-client.mjs",
20
- "es2020": "fesm2020/reveldigital-player-client.mjs",
21
- "esm2020": "esm2020/reveldigital-player-client.mjs",
22
- "fesm2020": "fesm2020/reveldigital-player-client.mjs",
23
- "fesm2015": "fesm2015/reveldigital-player-client.mjs",
24
- "typings": "reveldigital-player-client.d.ts",
25
- "exports": {
26
- "./package.json": {
27
- "default": "./package.json"
28
- },
29
- ".": {
30
- "types": "./reveldigital-player-client.d.ts",
31
- "esm2020": "./esm2020/reveldigital-player-client.mjs",
32
- "es2020": "./fesm2020/reveldigital-player-client.mjs",
33
- "es2015": "./fesm2015/reveldigital-player-client.mjs",
34
- "node": "./fesm2015/reveldigital-player-client.mjs",
35
- "default": "./fesm2020/reveldigital-player-client.mjs"
36
- }
37
- },
38
- "sideEffects": false
39
- }
1
+ {
2
+ "name": "@reveldigital/player-client",
3
+ "version": "0.0.16-beta.0",
4
+ "description": "Helper library for interfacing Angular apps with the Revel Digital player",
5
+ "repository": {
6
+ "url": "https://github.com/RevelDigital/reveldigital-client-library"
7
+ },
8
+ "author": {
9
+ "name": "Mike Tinnes"
10
+ },
11
+ "license": "",
12
+ "peerDependencies": {
13
+ "@angular/common": "^13.0.0",
14
+ "@angular/core": "^13.0.0",
15
+ "js-yaml": "^4.1.0"
16
+ },
17
+ "dependencies": {
18
+ "tslib": "^2.4.1"
19
+ }
20
+ }
@@ -0,0 +1,82 @@
1
+ import { Injectable, isDevMode } from '@angular/core';
2
+ import { HttpClient } from "@angular/common/http";
3
+ import yaml from "js-yaml";
4
+ import { ActivatedRoute, Router } from "@angular/router";
5
+
6
+
7
+ @Injectable({
8
+ providedIn: 'root'
9
+ })
10
+ export class AppInitService {
11
+
12
+ timestamp: any = new Date().getTime();
13
+
14
+ constructor(public http: HttpClient, private _route: ActivatedRoute, private _router: Router) {
15
+ let lastUptime = localStorage.getItem('uptime') || '30';
16
+ console.log('Last uptime was', lastUptime, 'seconds');
17
+ }
18
+
19
+ init(): Promise<any> {
20
+ return new Promise<void>(async (resolve) => {
21
+ if (isDevMode()) {
22
+ console.log('Development Mode');
23
+
24
+ /**
25
+ * Shim the shindig prefs functionality for dev mode
26
+ */
27
+ (<any>window).gadgets = {
28
+
29
+ Prefs: class {
30
+ getString(key: string) { return this.getParameterByName(key) }
31
+
32
+ getArray(key: string) { return this.getParameterByName(key).split(',') }
33
+
34
+ getBool(key: string) { return this.getParameterByName(key) === 'true' }
35
+
36
+ getCountry() { }
37
+
38
+ getFloat(key: string) { return parseFloat(this.getParameterByName(key)) }
39
+
40
+ getInt(key: string) { return parseInt(this.getParameterByName(key)) }
41
+
42
+ getLang() { }
43
+
44
+ getParameterByName(name: string, url = window.location.href): string {
45
+ name = name.replace(/[\[\]]/g, '\\$&');
46
+ let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
47
+ results = regex.exec(url);
48
+ if (!results) return '';
49
+ if (!results[2]) return '';
50
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
51
+ }
52
+ }
53
+ };
54
+
55
+ this.http.get('assets/user-prefs.yml', {
56
+ responseType: 'text'
57
+ }).subscribe(data => {
58
+ try {
59
+ const doc: any = yaml.load(data);
60
+ let params: any = {}
61
+ for (const val of doc.prefs) {
62
+ params[val.name] = val.default_value
63
+ }
64
+ this.navigate(params)
65
+ } catch (e) {
66
+ console.log(`Unable to load user preferences YAML definition file: ${e}`)
67
+ }
68
+ })
69
+ }
70
+ resolve();
71
+ });
72
+ }
73
+
74
+ navigate(params: any) {
75
+ // changes the route without moving from the current view or
76
+ // triggering a navigation event,
77
+ this._router.navigate([], {
78
+ relativeTo: this._route,
79
+ queryParams: params,
80
+ });
81
+ }
82
+ }
@@ -0,0 +1,27 @@
1
+ import { APP_INITIALIZER, NgModule } from '@angular/core';
2
+ import { PlayerClientService } from './player-client.service';
3
+ import { AppInitService } from './app-init.service';
4
+ import { HttpClientModule } from '@angular/common/http';
5
+ import { RouterModule, Routes } from '@angular/router';
6
+
7
+
8
+ @NgModule({
9
+ imports: [
10
+ HttpClientModule,
11
+ RouterModule.forRoot([])
12
+ ],
13
+ providers: [{
14
+ provide: APP_INITIALIZER,
15
+ useFactory: initializeApp,
16
+ deps: [AppInitService, PlayerClientService],
17
+ multi: true
18
+ }]
19
+ })
20
+ export class PlayerClientModule { }
21
+
22
+ function initializeApp(appInitService: AppInitService) {
23
+ return async () => {
24
+ PlayerClientService.init({});
25
+ await appInitService.init();
26
+ }
27
+ }
@@ -0,0 +1,16 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { PlayerClientService } from './player-client.service';
4
+
5
+ describe('PlayerClientService', () => {
6
+ let service: PlayerClientService;
7
+
8
+ beforeEach(() => {
9
+ TestBed.configureTestingModule({});
10
+ service = TestBed.inject(PlayerClientService);
11
+ });
12
+
13
+ it('should be created', () => {
14
+ expect(service).toBeTruthy();
15
+ });
16
+ });
@@ -0,0 +1,419 @@
1
+ import { Injectable, NgZone, OnDestroy, OnInit } from '@angular/core';
2
+ import { BehaviorSubject, fromEvent, Subject, Subscription } from 'rxjs';
3
+ import { filter, map, share, tap } from 'rxjs/operators';
4
+
5
+
6
+ // So that TypeScript doesn't complain, we're going to augment the GLOBAL / WINDOW
7
+ // name-space definition to include the Tracker API. This also provides us with a place
8
+ // to actually DOCUMENT the API so that our developers aren't guessing about what's
9
+ // available on the library.
10
+ declare global {
11
+ var Client: Client;
12
+ }
13
+
14
+ export interface Client {
15
+
16
+ callback(...args: any[]): void;
17
+
18
+ getDeviceTime(date?: Date): Promise<string>;
19
+
20
+ getDeviceTimeZoneName(): Promise<string>;
21
+
22
+ getDeviceTimeZoneID(): Promise<string>;
23
+
24
+ getDeviceTimeZoneOffset(): Promise<number>;
25
+
26
+ getLanguageCode(): Promise<string>;
27
+
28
+ getDeviceKey(): Promise<string>;
29
+
30
+ sendCommand(name: string, arg: string): void;
31
+
32
+ sendRemoteCommand(deviceKeys: string[], name: string, arg: string): void;
33
+
34
+ track(eventName: string, properties?: string): void;
35
+
36
+ timeEvent(eventName: string): void;
37
+
38
+ newEventSession(id?: string): void;
39
+
40
+ getRevelRoot(): Promise<string>;
41
+
42
+ getCommandMap(): Promise<string>;
43
+ }
44
+
45
+ export interface EventProperties {
46
+ [key: string]: any;
47
+ }
48
+
49
+ export interface Command {
50
+ name: string;
51
+ arg: string;
52
+ }
53
+
54
+ @Injectable({
55
+ providedIn: 'root'
56
+ })
57
+ export class PlayerClientService implements OnDestroy {
58
+
59
+ private clientPromise: Promise<Client> | null;
60
+
61
+ public onCommand$ = new Subject<Command>();
62
+ public onReady$ = new BehaviorSubject(false);
63
+ public onStart$ = new Subject();
64
+ public onStop$ = new Subject();
65
+
66
+ //
67
+ // Three methods available for calling into the library:
68
+ //
69
+ // 1) Using dispatchEvent() with the following custom events
70
+ // 2) Using the window scoped RevelDigital object as defined in the constructor
71
+ //
72
+ private onStartSub: Subscription;
73
+ private onStartEvt$ = fromEvent(document, 'RevelDigital.Start').pipe(
74
+ share(),
75
+ tap(this.onStart$)
76
+ );
77
+ private onStopSub: Subscription;
78
+ private onStopEvt$ = fromEvent(document, 'RevelDigital.Stop').pipe(
79
+ share(),
80
+ tap(this.onStop$)
81
+ );
82
+ private onCommandSub: Subscription;
83
+ private onCommandEvt$ = fromEvent<Command>(document, 'RevelDigital.Command').pipe(
84
+ map((e: any) => { return { name: e.detail.name, arg: e.detail.arg } as Command }),
85
+ share(),
86
+ tap(this.onCommand$)
87
+ );
88
+
89
+ // private onPostMessageSub: Subscription;
90
+ // private onPostMessageEvt$ = fromEvent(window, 'message').pipe(
91
+ // filter((messageEvent: MessageEvent) =>
92
+ // messageEvent.source !== window.parent &&
93
+ // typeof messageEvent.data === 'string' &&
94
+ // messageEvent.data.startsWith('reveldigital:')),
95
+ // map((e: any) => { return JSON.parse(e.substring(13)) as Command }),
96
+ // share(),
97
+ // tap(this.onCommand$)
98
+ // );
99
+
100
+
101
+ constructor(zone: NgZone) {
102
+
103
+ let self = this;
104
+ (window as any).RevelDigital = {
105
+ Controller: {
106
+ onCommand: function (name: string, arg: string) {
107
+ zone.run(() => {
108
+ self.onCommand$.next({ name: name, arg: arg });
109
+ });
110
+ },
111
+ onStart: function () {
112
+ zone.run(() => {
113
+ self.onStart$.next();
114
+ });
115
+ },
116
+ onStop: function () {
117
+ zone.run(() => {
118
+ self.onStop$.next();
119
+ });
120
+ }
121
+ }
122
+ }
123
+
124
+ this.onStartSub = this.onStartEvt$.subscribe(() => { });
125
+ this.onStopSub = this.onStopEvt$.subscribe(() => { });
126
+ this.onCommandSub = this.onCommandEvt$.subscribe(() => { });
127
+
128
+ this.clientPromise = null;
129
+
130
+ this.onReady$.next(true);
131
+ }
132
+
133
+ ngOnDestroy(): void {
134
+
135
+ this.onStartSub?.unsubscribe();
136
+ this.onStopSub?.unsubscribe();
137
+ this.onCommandSub?.unsubscribe();
138
+
139
+ this.onReady$.next(false);
140
+ }
141
+
142
+ public static init(data: any) {
143
+
144
+ console.log("init()");
145
+ }
146
+
147
+ public callback(...args: any[]): void {
148
+
149
+ this.getClient().then((client) => {
150
+
151
+ switch (args.length) {
152
+ case 0:
153
+ client.callback();
154
+ break;
155
+ case 1:
156
+ client.callback(args[0]);
157
+ break;
158
+ case 2:
159
+ client.callback(args[1]);
160
+ break;
161
+ case 3:
162
+ client.callback(args[2]);
163
+ break;
164
+ case 4:
165
+ client.callback(args[3]);
166
+ break;
167
+ case 5:
168
+ client.callback(args[4]);
169
+ break;
170
+ }
171
+ })
172
+ }
173
+
174
+ public async getDeviceTime(date?: Date): Promise<string> {
175
+
176
+ const client = await this.getClient();
177
+
178
+ if (date !== undefined) {
179
+ return client.getDeviceTime(date);
180
+ }
181
+ return client.getDeviceTime();
182
+ }
183
+
184
+ public async getDeviceTimeZoneName(): Promise<string> {
185
+
186
+ const client = await this.getClient();
187
+
188
+ return client.getDeviceTimeZoneName();
189
+ }
190
+
191
+ public async getDeviceTimeZoneID(): Promise<string> {
192
+
193
+ const client = await this.getClient();
194
+
195
+ return client.getDeviceTimeZoneID();
196
+ }
197
+
198
+ public async getDeviceTimeZoneOffset(): Promise<number> {
199
+
200
+ const client = await this.getClient();
201
+
202
+ return client.getDeviceTimeZoneOffset();
203
+ }
204
+
205
+ public async getLanguageCode(): Promise<string> {
206
+
207
+ const client = await this.getClient();
208
+
209
+ return client.getLanguageCode();
210
+ }
211
+
212
+ public async getDeviceKey(): Promise<string> {
213
+
214
+ const client = await this.getClient();
215
+
216
+ return client.getDeviceKey();
217
+ }
218
+
219
+ public sendCommand(name: string, arg: string): void {
220
+
221
+ this.getClient().then((client) => {
222
+ client.sendCommand(name, arg);
223
+ })
224
+ }
225
+
226
+ public sendRemoteCommand(deviceKeys: string[], name: string, arg: string): void {
227
+
228
+ this.getClient().then((client) => {
229
+ client.sendRemoteCommand(deviceKeys, name, arg);
230
+ });
231
+ }
232
+
233
+ public track(eventName: string, properties?: EventProperties): void {
234
+
235
+ this.getClient().then((client) => {
236
+ client.track(eventName, JSON.stringify(properties));
237
+ })
238
+ }
239
+
240
+ public timeEvent(eventName: string): void {
241
+
242
+ this.getClient().then((client) => {
243
+ client.timeEvent(eventName);
244
+ })
245
+ }
246
+
247
+ public newEventSession(id?: string): void {
248
+
249
+ this.getClient().then((client) => {
250
+ if (id !== undefined) {
251
+ client.newEventSession();
252
+ } else {
253
+ client.newEventSession(id);
254
+ }
255
+ })
256
+ }
257
+
258
+ public async getRevelRoot(): Promise<string> {
259
+
260
+ const client = await this.getClient();
261
+
262
+ return client.getRevelRoot();
263
+ }
264
+
265
+ public async getCommandMap(): Promise<any> {
266
+
267
+ const client = await this.getClient();
268
+
269
+ return JSON.parse(await client.getCommandMap());
270
+
271
+ // let map = new Map<string, any>();
272
+
273
+ // let obj = JSON.parse(await client.getCommandMap());
274
+ // for (let key in obj) {
275
+ // map.set(key, obj[key]);
276
+ // }
277
+ // return map;
278
+ }
279
+
280
+ // ---
281
+ // PRIVATE METHODS.
282
+ // ---
283
+
284
+ private getClient(): Promise<Client> {
285
+
286
+ if (this.clientPromise) {
287
+
288
+ return (this.clientPromise);
289
+ }
290
+
291
+ if (window.Client) {
292
+
293
+ return (this.clientPromise = Promise.resolve(window.Client));
294
+ }
295
+
296
+ // A "complete" status indicates that the "load" event has been fired on the
297
+ // window; and, that all sub-resources such as Scripts, Images, and Frames have
298
+ // been loaded.
299
+ if (window.document.readyState === "complete") {
300
+
301
+ // If this event has fired AND the 3rd-party script isn't available (see IF-
302
+ // condition BEFORE this one), it means that the 3rd-party script either
303
+ // failed on the network or was BLOCKED by an ad-blocker. As such, we have to
304
+ // fall-back to using a mock API.
305
+ return (this.clientPromise = Promise.resolve(new NoopClient()));
306
+ }
307
+
308
+ // ASSERT: If we made it this far, the document has not completed loading (but it
309
+ // may be in an "interactive" state which is when I believe that the Angular app
310
+ // gets bootstrapped). As such, we need bind to the LOAD event to wait for our
311
+ // third-party scripts to load (or fail to load, or be blocked).
312
+ this.clientPromise = new Promise<Client>(
313
+ (resolve) => {
314
+
315
+ window.addEventListener(
316
+ "load",
317
+ function handleWindowLoad() {
318
+
319
+ // At this point, the 3rd-party library is either available or
320
+ // it's not - there's no further loading to do. If it's not
321
+ // present on the global scope, we're going to fall-back to using
322
+ // a mock API.
323
+ resolve(window.Client || new NoopClient());
324
+ }
325
+ );
326
+
327
+ }
328
+ );
329
+
330
+ return (this.clientPromise);
331
+ }
332
+
333
+ }
334
+
335
+
336
+
337
+ // ----------------------------------------------------------------------------------- //
338
+ // ----------------------------------------------------------------------------------- //
339
+
340
+ // I provide a mock API for the 3rd-party script. This just allows the consuming code to
341
+ // act as though the library is available even if it failed to load (example, it was
342
+ // blocked by an ad-blocker).
343
+ class NoopClient implements Client {
344
+
345
+ constructor() {
346
+
347
+ console.warn("Client API not available, falling back to mock API.");
348
+ }
349
+
350
+ public callback(...args: any[]): void {
351
+
352
+ // NOOP implement, nothing to do....
353
+ }
354
+
355
+ public getDeviceTime(date?: Date): Promise<string> {
356
+
357
+ return Promise.resolve(null);
358
+ }
359
+
360
+ public async getDeviceTimeZoneName(): Promise<string> {
361
+
362
+ return Promise.resolve(null);
363
+ }
364
+
365
+ public async getDeviceTimeZoneID(): Promise<string> {
366
+
367
+ return Promise.resolve(null);
368
+ }
369
+
370
+ public async getDeviceTimeZoneOffset(): Promise<number> {
371
+
372
+ return Promise.resolve(null);
373
+ }
374
+
375
+ public async getLanguageCode(): Promise<string> {
376
+
377
+ return Promise.resolve(null);
378
+ }
379
+
380
+ public async getDeviceKey(): Promise<string> {
381
+
382
+ return Promise.resolve(null);
383
+ }
384
+
385
+ public sendCommand(name: string, arg: string): void {
386
+
387
+ // NOOP implement, nothing to do....
388
+ }
389
+
390
+ public sendRemoteCommand(deviceKeys: string[], name: string, arg: string) {
391
+
392
+ // NOOP implement, nothing to do....
393
+ }
394
+
395
+ public track(eventName: string, properties?: string): void {
396
+
397
+ // NOOP implement, nothing to do....
398
+ }
399
+
400
+ public timeEvent(eventName: string): void {
401
+
402
+ // NOOP implement, nothing to do....
403
+ }
404
+
405
+ public newEventSession(id?: string): void {
406
+
407
+ // NOOP implement, nothing to do....
408
+ }
409
+
410
+ public async getRevelRoot(): Promise<string> {
411
+
412
+ return Promise.resolve(null);
413
+ }
414
+
415
+ public async getCommandMap(): Promise<string> {
416
+
417
+ return Promise.resolve('{}');
418
+ }
419
+ }
@@ -1,2 +1,6 @@
1
- export * from './lib/player-client.service';
2
- export * from './lib/player-client.module';
1
+ /*
2
+ * Public API Surface of player-client
3
+ */
4
+
5
+ export * from './lib/player-client.service';
6
+ export * from './lib/player-client.module';
package/src/test.ts ADDED
@@ -0,0 +1,26 @@
1
+ // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2
+
3
+ import 'zone.js';
4
+ import 'zone.js/testing';
5
+ import { getTestBed } from '@angular/core/testing';
6
+ import {
7
+ BrowserDynamicTestingModule,
8
+ platformBrowserDynamicTesting
9
+ } from '@angular/platform-browser-dynamic/testing';
10
+
11
+ declare const require: {
12
+ context(path: string, deep?: boolean, filter?: RegExp): {
13
+ keys(): string[];
14
+ <T>(id: string): T;
15
+ };
16
+ };
17
+
18
+ // First, initialize the Angular testing environment.
19
+ getTestBed().initTestEnvironment(
20
+ BrowserDynamicTestingModule,
21
+ platformBrowserDynamicTesting()
22
+ );
23
+ // Then we find all the tests.
24
+ const context = require.context('./', true, /\.spec\.ts$/);
25
+ // And load the modules.
26
+ context.keys().map(context);