@m2c2kit/cli 0.1.11 → 0.3.3

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.
@@ -1,24 +1,14 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "es6",
4
- "lib": [
5
- "es6",
6
- "dom",
7
- "dom.iterable"
8
- ],
4
+ "lib": ["es6", "dom", "dom.iterable"],
9
5
  "module": "es6",
10
6
  "moduleResolution": "node",
11
- "allowJs": true,
7
+ "allowJs": true,
12
8
  "esModuleInterop": true,
13
9
  "forceConsistentCasingInFileNames": true,
14
10
  "strict": true,
15
- "skipLibCheck": true
11
+ "skipLibCheck": true,
16
12
  },
17
- "exclude": [
18
- "node_modules",
19
- "build",
20
- "dist",
21
- "**/{{appName}}.bundle.js",
22
- "rollup.config.js"
23
- ]
13
+ "exclude": ["node_modules", "build", "dist", "rollup.config.mjs", "post-install.mjs"]
24
14
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@m2c2kit/cli",
3
- "version": "0.1.11",
3
+ "version": "0.3.3",
4
4
  "description": "m2c2kit command line interface",
5
5
  "module": "dist/cli.js",
6
6
  "files": [
7
- "dist"
7
+ "dist/**"
8
8
  ],
9
9
  "type": "module",
10
10
  "bin": {
@@ -16,29 +16,29 @@
16
16
  "scripts": {
17
17
  "build": "npm run clean && npm run compile && npm run copy-files && npm run write-dotenv",
18
18
  "compile": "tsc",
19
- "clean": "rimraf dist/ && rimraf build/",
20
- "copy-files": "copyfiles -f build/src/cli.js dist && copyfiles scripts/* templates/* fonts/**/* dist/",
19
+ "clean": "rimraf dist build/",
20
+ "copy-files": "cpy --flat build/src/*.js dist && cpy templates assets dist",
21
21
  "write-dotenv": "node write-dotenv.js"
22
22
  },
23
23
  "author": "",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "axios": "0.24.0",
27
- "chalk": "5.0.1",
28
- "conf": "10.1.1",
26
+ "axios": "1.2.4",
27
+ "chalk": "5.2.0",
28
+ "conf": "11.0.1",
29
29
  "form-data": "4.0.0",
30
30
  "handlebars": "4.7.7",
31
- "ora": "6.1.0",
31
+ "ora": "6.3.0",
32
32
  "prompts": "2.4.2",
33
- "yargs": "17.3.1"
33
+ "yargs": "17.7.1"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/node": "17.0.21",
37
- "@types/prompts": "2.0.14",
38
- "@types/yargs": "17.0.9",
39
- "copyfiles": "2.4.1",
40
- "rimraf": "3.0.2",
41
- "tslib": "2.3.1",
42
- "typescript": "4.6.2"
36
+ "@types/node": "18.15.11",
37
+ "@types/prompts": "2.4.4",
38
+ "@types/yargs": "17.0.24",
39
+ "cpy-cli": "4.2.0",
40
+ "rimraf": "5.0.0",
41
+ "tslib": "2.5.0",
42
+ "typescript": "5.0.4"
43
43
  }
44
44
  }
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "fs";
3
- import { resolve, basename } from "path";
4
-
5
- const { dependencies } = JSON.parse(fs.readFileSync("./package.json"));
6
-
7
- /** If this m2c2kit app uses surveys, then copy the required css
8
- * files from node_modules so they can be bundled */
9
- if (Object.keys(dependencies).includes("@m2c2kit/survey")) {
10
- // does ./css folder exist? if not, create
11
- if (!fs.existsSync("./css")) {
12
- fs.mkdirSync("./css");
13
- }
14
-
15
- const cssDistDir = "./node_modules/@m2c2kit/survey/dist/css/";
16
- const cssFiles = fs
17
- .readdirSync(cssDistDir, {
18
- withFileTypes: true,
19
- })
20
- .filter((dirent) => !dirent.isDirectory())
21
- .map((dirent) => resolve(cssDistDir, dirent.name));
22
-
23
- cssFiles.forEach((file) => {
24
- const sourceContents = fs.readFileSync(file);
25
- fs.writeFileSync(`./css/${basename(file)}`, sourceContents);
26
- });
27
- }
@@ -1,97 +0,0 @@
1
- import typescript from "@rollup/plugin-typescript";
2
- import nodeResolve from "@rollup/plugin-node-resolve";
3
- import commonjs from "@rollup/plugin-commonjs";
4
- import shim from "rollup-plugin-shim";
5
- import copy from "rollup-plugin-copy";
6
- import serve from "rollup-plugin-serve";
7
- import livereload from "rollup-plugin-livereload";
8
- import del from "rollup-plugin-delete";
9
-
10
- let sharedPlugins = [
11
- // canvaskit-wasm references these node.js functions
12
- // shim them to empty functions for browser usage
13
- shim({
14
- fs: `export function fs_empty_shim() { }`,
15
- path: `export function path_empty_shim() { }`,
16
- }),
17
- nodeResolve(),
18
- commonjs({
19
- include: "node_modules/canvaskit-wasm/**",
20
- }),
21
- ];
22
-
23
- export default (commandLineArgs) => {
24
-
25
- let outputFolder = "build";
26
- if (commandLineArgs.configProd) {
27
- outputFolder = "dist"
28
- }
29
-
30
- const finalConfig = [
31
- {
32
- input: "./src/{{appName}}.ts",
33
- output: [
34
- {
35
- file: `./${outputFolder}/{{appName}}.bundle.js`,
36
- format: "esm",
37
- sourcemap: commandLineArgs.configServe && true,
38
- },
39
- ],
40
- plugins: [
41
- commandLineArgs.configProd && del({ targets: [outputFolder] }),
42
- ...sharedPlugins,
43
- typescript({
44
- inlineSourceMap: commandLineArgs.configServe && true,
45
- inlineSources: commandLineArgs.configServe && true,
46
- }),
47
- copy({
48
- targets: [
49
- // copy the wasm bundle out of node_modules so it can be served
50
- {
51
- src: "node_modules/canvaskit-wasm/bin/canvaskit.wasm",
52
- dest: outputFolder,
53
- },
54
- {
55
- src: "fonts/*",
56
- dest: `${outputFolder}/fonts`,
57
- },
58
- {
59
- src: "img/*",
60
- dest: `${outputFolder}/img`,
61
- },
62
- {
63
- src: "css/*",
64
- dest: `${outputFolder}/css`,
65
- },
66
- ],
67
- copyOnce: false,
68
- hook: "writeBundle",
69
- }),
70
- copy({
71
- targets: [
72
- {
73
- src: "src/index.html",
74
- dest: outputFolder,
75
- },
76
- ],
77
- copyOnce: false,
78
- hook: "writeBundle",
79
- }),
80
- commandLineArgs.configServe &&
81
- serve({
82
- // we can start development server and automatically open browser by running
83
- // npm run serve -- --configOpen
84
- open: commandLineArgs.configOpen && true,
85
- verbose: true,
86
- contentBase: [`./${outputFolder}`],
87
- historyApiFallback: true,
88
- host: "localhost",
89
- port: 3000,
90
- }),
91
- commandLineArgs.configServe &&
92
- livereload({ watch: "build", delay: 0 }),
93
- ],
94
- },
95
- ];
96
- return finalConfig;
97
- };
@@ -1,426 +0,0 @@
1
- import {
2
- Game,
3
- Action,
4
- Scene,
5
- Shape,
6
- Label,
7
- WebColors,
8
- LabelHorizontalAlignmentMode,
9
- GameParameters,
10
- GameOptions,
11
- TrialSchema,
12
- Session,
13
- SessionLifecycleEvent,
14
- ActivityDataEvent,
15
- ActivityLifecycleEvent,
16
- Sprite,
17
- Timer,
18
- } from "@m2c2kit/core";
19
- import { Button, Instructions } from "@m2c2kit/addons";
20
-
21
- class {{className}} extends Game {
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- constructor() {
24
- /**
25
- * These are configurable game parameters and their defaults.
26
- * At run time, they can be changed with the setParameters() method.
27
- */
28
- const defaultParameters: GameParameters = {
29
- ReadyTime: {
30
- value: 1000,
31
- description:
32
- "How long the 'get ready' message scene is shown, milliseconds",
33
- },
34
- TrialNum: { value: 3, description: "How many trials to run" },
35
- };
36
-
37
- /**
38
- * This describes all the data that will be generated by the assessment.
39
- * At runtime, when a trial completes, the data will be returned to the
40
- * session with a callback, along with this schema transformed into
41
- * JSON Schema Draft-07 format.
42
- */
43
- const demoTrialSchema: TrialSchema = {
44
- colorChosen: { type: "string", description: "the color that was picked" },
45
- correct: { type: "boolean", description: "was the answer correct?" },
46
- responseTime: { type: "number", description: "response time (ms) to choose shape" }
47
- };
48
-
49
- const options: GameOptions = {
50
- name: "{{appName}}",
51
- version: "1.0.0",
52
- uri: "https://your-repo-or-webpage-here",
53
- shortDescription: "A brief couple sentence description.",
54
- longDescription: "An extended, many-sentence description.",
55
- showFps: false,
56
- trialSchema: demoTrialSchema,
57
- parameters: defaultParameters,
58
- // You can set this color so we can see the boundaries of the game during development,
59
- // but typically we would not set this, so it is commented out
60
- // bodyBackgroundColor: WebColors.Wheat,
61
- // note: using 2:1 aspect ratio, because that is closer to modern phones
62
- width: 400,
63
- height: 800,
64
- // set stretch to true if you want to fill the screen on large windows
65
- // (e.g., iPad, desktop browser)
66
- stretch: false,
67
- // Roboto is included by default. Leave fontUrls unchanged, unless you want to use different font
68
- fontUrls: ["./fonts/roboto/Roboto-Regular.ttf"],
69
- // for each image below, you specify either a tag of an svg in the property "svgString"
70
- // or you specify a URL to an image in the property "url",
71
- // such as url: 'https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg'
72
- images: [
73
- {
74
- name: "star",
75
- height: 60,
76
- width: 60,
77
- svgString:
78
- '<svg xmlns="http://www.w3.org/2000/svg" width="304" height="290"> <path d="M2,111 h300 l-242.7,176.3 92.7,-285.3 92.7,285.3 z" style="fill:#00FF00;stroke:#0000FF;stroke-width:15;stroke-linejoin:round"/></svg>',
79
- },
80
- {
81
- name: "smiley",
82
- height: 64,
83
- width: 64,
84
- svgString:
85
- '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="451.451 -28.549 1657.098 1657.098" enable-background="new 451.451 -28.549 1657.098 1657.098" xml:space="preserve"><g><g><circle cx="992.682" cy="640.47" r="66.818"/></g><g><circle cx="1558.728" cy="640.47" r="66.818"/></g><g><path d="M1280,1600c-107.984,0-212.756-21.157-311.407-62.883c-95.268-40.295-180.819-97.973-254.279-171.432c-73.459-73.459-131.137-159.01-171.432-254.278C501.157,1012.757,480,907.984,480,800s21.157-212.756,62.883-311.407c40.295-95.268,97.973-180.819,171.432-254.279c73.46-73.459,159.011-131.137,254.279-171.432C1067.244,21.157,1172.016,0,1280,0s212.757,21.157,311.407,62.883c95.268,40.295,180.819,97.973,254.278,171.432c73.459,73.46,131.137,159.011,171.432,254.279C2058.843,587.244,2080,692.016,2080,800s-21.157,212.757-62.883,311.407c-40.295,95.268-97.973,180.819-171.432,254.278s-159.01,131.137-254.278,171.432C1492.757,1578.843,1387.984,1600,1280,1600z M1280,71.591c-401.646,0-728.409,326.763-728.409,728.409S878.354,1528.409,1280,1528.409S2008.409,1201.646,2008.409,800C2008.409,398.354,1681.646,71.591,1280,71.591z"/></g><g><path d="M1665.953,1071.004c-213.156,213.156-558.75,213.156-771.905,0c20.534-20.534,41.068-41.068,61.602-61.602c179.134,179.134,469.567,179.134,648.7,0C1624.884,1029.936,1645.418,1050.47,1665.953,1071.004z"/></g><g><path d="M1167.066,463.279c-96.31-96.31-252.459-96.31-348.768,0c16.971,16.971,33.941,33.941,50.912,50.912c68.192-68.192,178.753-68.192,246.945,0C1133.125,497.22,1150.096,480.249,1167.066,463.279z"/></g><g><path d="M1733.112,463.279c-96.31-96.31-252.459-96.31-348.768,0c16.971,16.971,33.941,33.941,50.912,50.912c68.192-68.192,178.753-68.192,246.945,0C1699.171,497.22,1716.142,480.249,1733.112,463.279z"/></g></g></svg>',
86
- },
87
- ],
88
- };
89
-
90
- super(options);
91
- }
92
-
93
- init(): void {
94
- // just for convenience, alias the variable game to "this"
95
- // (even though eslint doesn't like it)
96
- // eslint-disable-next-line @typescript-eslint/no-this-alias
97
- const game = this;
98
-
99
- const instructionsScenes = Instructions.Create({
100
- sceneNamePrefix: "instructions",
101
- backgroundColor: WebColors.White,
102
- nextButtonBackgroundColor: WebColors.Black,
103
- backButtonBackgroundColor: WebColors.Black,
104
- instructionScenes: [
105
- {
106
- title: "{{appName}} Demo",
107
- text: "For this task, you will click the red rectangle. Open the developer console to see the trial data and other output.",
108
- textFontSize: 24,
109
- titleFontSize: 30,
110
- image: "smiley",
111
- imageMarginBottom: 24
112
- },
113
- {
114
- title: "{{appName}} Demo",
115
- text: "Press START to begin!",
116
- textFontSize: 24,
117
- titleFontSize: 30,
118
- textAlignmentMode: LabelHorizontalAlignmentMode.center,
119
- textVerticalBias: 0.25,
120
- nextButtonText: "START",
121
- nextButtonBackgroundColor: WebColors.Green,
122
- },
123
- ],
124
- // this is the name of the scene to go to after the last instructions scene
125
- postInstructionsScene: "getReady",
126
- });
127
- game.addScenes(instructionsScenes);
128
-
129
- const getReadyScene = new Scene({
130
- // Because this is the scene after the instructions, we must give
131
- // it a name and provide it to postInstructionsScene, above
132
- name: "getReady",
133
- backgroundColor: WebColors.White,
134
- });
135
- game.addScene(getReadyScene);
136
-
137
- const getReadyMessage = new Label({
138
- text: "Get Ready",
139
- fontSize: 24,
140
- position: { x: 200, y: 400 },
141
- });
142
- getReadyScene.addChild(getReadyMessage);
143
-
144
- // example of how to use an image. The image must be previously loaded
145
- const starSprite = new Sprite({
146
- imageName: "star",
147
- position: { x: 200, y: 500 },
148
- });
149
- getReadyScene.addChild(starSprite);
150
-
151
- // getReadyScene.onSetup() has a callback that is executed each time this scene is shown
152
- getReadyScene.onSetup(() => {
153
- getReadyScene.run(
154
- Action.Sequence([
155
- // Get the wait duration from the default game parameters, defined above
156
- Action.Wait({ duration: game.getParameter("ReadyTime") }),
157
- Action.Custom({
158
- callback: () => {
159
- game.presentScene(chooseRectangleScene);
160
- },
161
- }),
162
- ])
163
- );
164
- });
165
-
166
- // these entities before the onSetup() can be defined outside of a onSetup()
167
- // because they exist through multiple trials
168
- // Their position and how they respond to interactions may differ across trials,
169
- // and that logic will be written within a onSetup()
170
- const chooseRectangleScene = new Scene({
171
- backgroundColor: WebColors.LightGray,
172
- });
173
- game.addScene(chooseRectangleScene);
174
- const redRect = new Shape({
175
- rect: { width: 150, height: 100 },
176
- fillColor: WebColors.Red,
177
- });
178
- chooseRectangleScene.addChild(redRect);
179
- const blueRect = new Shape({
180
- rect: { width: 150, height: 100 },
181
- fillColor: WebColors.Blue,
182
- });
183
- chooseRectangleScene.addChild(blueRect);
184
- const correctMessage = new Label({
185
- text: "CORRECT!",
186
- position: { x: 200, y: 500 },
187
- });
188
-
189
- const chooseMessage = new Label({
190
- text: "Choose the red rectangle",
191
- position: { x: 200, y: 200 },
192
- });
193
- chooseRectangleScene.addChild(chooseMessage);
194
-
195
- correctMessage.hidden = true;
196
- chooseRectangleScene.addChild(correctMessage);
197
- const wrongMessage = new Label({
198
- text: "WRONG!",
199
- position: {x: 200, y: 500 },
200
- });
201
-
202
- wrongMessage.hidden = true;
203
- chooseRectangleScene.addChild(wrongMessage);
204
-
205
- // chooseRectangleScene.onSetup() is passed a callback that is executed each
206
- // time this scene is shown. Within onSetup(), We will randomly decide on
207
- // what side the red rectangle is shown
208
- chooseRectangleScene.onSetup(() => {
209
- let responseTime = NaN;
210
- let redOnLeft = true;
211
- if (Math.random() > 0.5) {
212
- redOnLeft = false;
213
- }
214
-
215
- if (redOnLeft) {
216
- redRect.position = { x: 100, y: 300 };
217
- blueRect.position = { x: 300, y: 300 };
218
- } else {
219
- redRect.position = { x: 300, y: 300 };
220
- blueRect.position = { x: 100, y: 300 };;
221
- }
222
-
223
- // helper function to record the user's choice and
224
- // decide if we are done
225
- function recordUserInput(choseRedRect: boolean) {
226
- game.addTrialData("responseTime", responseTime);
227
- game.addTrialData("correct", choseRedRect);
228
- if (choseRedRect) {
229
- game.addTrialData("colorChosen", "red");
230
- } else {
231
- game.addTrialData("colorChosen", "blue");
232
- }
233
- game.trialComplete();
234
- // have we finished the number of trials?
235
- if (game.trialIndex < game.getParameter<number>("TrialNum")) {
236
- game.presentScene(getReadyScene);
237
- } else {
238
- game.presentScene(endScene);
239
- }
240
- }
241
-
242
- redRect.isUserInteractionEnabled = true;
243
- redRect.onTapDown(() => {
244
- redRect.run(
245
- Action.Sequence([
246
- Action.Custom({
247
- callback: () => {
248
- responseTime = Timer.elapsed("rt");
249
- // once a choice is made, don't allow additional taps
250
- redRect.isUserInteractionEnabled = false;
251
- blueRect.isUserInteractionEnabled = false;
252
- },
253
- }),
254
- // short animation to shrink and grow button
255
- Action.Scale({ scale: 0.8, duration: 250 }),
256
- Action.Scale({ scale: 1, duration: 250 }),
257
- // Show the "CORRECT" message
258
- Action.Custom({
259
- callback: () => {
260
- correctMessage.hidden = false;
261
- },
262
- }),
263
- Action.Wait({ duration: 1000 }),
264
- // Handle the logic of recording data, and deciding if we should
265
- // do another trial in recordUserInput
266
- Action.Custom({
267
- callback: () => {
268
- correctMessage.hidden = true;
269
- recordUserInput(true);
270
- },
271
- }),
272
- ])
273
- );
274
- });
275
-
276
- blueRect.isUserInteractionEnabled = true;
277
- blueRect.onTapDown(() => {
278
- blueRect.run(
279
- Action.Sequence([
280
- Action.Custom({
281
- callback: () => {
282
- responseTime = Timer.elapsed("rt");
283
- redRect.isUserInteractionEnabled = false;
284
- blueRect.isUserInteractionEnabled = false;
285
- },
286
- }),
287
- Action.Scale({ scale: 0.8, duration: 250 }),
288
- Action.Scale({ scale: 1, duration: 250 }),
289
- Action.Custom({
290
- callback: () => {
291
- wrongMessage.hidden = false;
292
- },
293
- }),
294
- Action.Wait({ duration: 1000 }),
295
- Action.Custom({
296
- callback: () => {
297
- wrongMessage.hidden = true;
298
- recordUserInput(false);
299
- },
300
- }),
301
- ])
302
- );
303
- });
304
- });
305
-
306
- chooseRectangleScene.onAppear(() => {
307
- Timer.removeAll();
308
- Timer.start("rt");
309
- });
310
-
311
- const endScene = new Scene();
312
- game.addScene(endScene);
313
- const doneLabel = new Label({
314
- text: `This will be reassigned in the onSetup() callback. If you see this, something went wrong!`,
315
- position: { x: 200, y: 300},
316
- });
317
- endScene.addChild(doneLabel);
318
-
319
- const startOverButton = new Button({
320
- text: "Start over",
321
- position: { x: 200, y: 600 },
322
- });
323
- startOverButton.isUserInteractionEnabled = true;
324
- startOverButton.onTapDown(() => {
325
- game.initData();
326
- game.presentScene(getReadyScene);
327
- });
328
- endScene.addChild(startOverButton);
329
-
330
- const exitButton = new Button({
331
- text: "Exit",
332
- position: { x: 200, y: 675 },
333
- });
334
- exitButton.isUserInteractionEnabled = true;
335
- exitButton.onTapDown(() => {
336
- // hide the start over button
337
- startOverButton.hidden = true;
338
- // don't allow repeated taps on exit button
339
- exitButton.isUserInteractionEnabled = false;
340
- game.end();
341
- });
342
- endScene.addChild(exitButton);
343
-
344
- endScene.onSetup(() => {
345
- doneLabel.text = `You did ${game.trialIndex} trials. You're done!`;
346
- });
347
-
348
- game.entryScene = "instructions-01";
349
- }
350
- }
351
-
352
- const game1 = new {{className}}();
353
- // default was 3 trials; this is how we can specify a different value
354
- game1.setParameters({ TrialNum: 2 });
355
-
356
- const session = new Session({
357
- activities: [game1],
358
- sessionCallbacks: {
359
- /**
360
- * onSessionLifecycleChange() will be called on events such
361
- * as when the session initialization is complete or when it
362
- * ends.
363
- *
364
- * Once initialized, the session will automatically start,
365
- * unless we're running in an Android WebView AND a manual start
366
- * is desired.
367
- */
368
- onSessionLifecycleChange: (ev: SessionLifecycleEvent) => {
369
- if (ev.initialized) {
370
- session.start();
371
- }
372
- if (ev.ended) {
373
- console.log("session ended");
374
- }
375
- },
376
- },
377
- activityCallbacks: {
378
- /**
379
- * onActivityDataCreate() is where you insert code to post data to an API
380
- * or interop with a native function in the host app.
381
- *
382
- * newData is the data that was just generated by the completed trial
383
- * data is all the data, cumulative of all trials, that have been generated.
384
- *
385
- * We separate out newData from data in case you want to alter the execution
386
- * based on the most recent trial, e.g., maybe you want to stop after
387
- * a certain user behavior or performance threshold in the just completed
388
- * trial.
389
- */
390
- onActivityDataCreate: (ev: ActivityDataEvent) => {
391
- console.log(`********** trial complete`);
392
- console.log("newData: " + JSON.stringify(ev.newData));
393
- console.log("newData schema: " + JSON.stringify(ev.newDataSchema));
394
- console.log("data: " + JSON.stringify(ev.data));
395
- console.log("data schema: " + JSON.stringify(ev.dataSchema));
396
- console.log(
397
- "activity parameters: " + JSON.stringify(ev.activityConfiguration)
398
- );
399
- },
400
- /**
401
- * onActivityLifecycleChange() notifies us when an activity, such
402
- * as an assessment or a survey, has completed. Usually, however,
403
- * we want to know when all the activities are done, so we'll
404
- * look for the session ending via onSessionLifecycleChange
405
- */
406
- onActivityLifecycleChange: (ev: ActivityLifecycleEvent) => {
407
- if (ev.ended) {
408
- console.log(`ended activity ${ev.name}`);
409
- if (session.nextActivity) {
410
- session.advanceToNextActivity();
411
- } else {
412
- session.end();
413
- }
414
- }
415
- },
416
- },
417
- });
418
-
419
- /**
420
- * Make session also available on window in case we want to control
421
- * the session through another means, such as other javascript or
422
- * browser code, or the Android WebView loadUrl() method
423
- * */
424
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
425
- (window as unknown as any).session = session;
426
- session.init();