From c02602d459eee1b61559cfe18d7e4fbcee2c877b Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Fri, 10 May 2019 18:26:28 +0900 Subject: [PATCH 01/10] Support to run local game.json --- package-lock.json | 28 +- .../headless-driver-runner-v1/package.json | 6 +- .../src/platform/assets/NodeScriptAsset.ts | 5 +- .../src/platform/assets/NodeTextAsset.ts | 5 +- .../headless-driver-runner-v2/package.json | 6 +- .../src/platform/assets/NodeScriptAsset.ts | 9 +- .../src/platform/assets/NodeTextAsset.ts | 5 +- .../headless-driver-runner/package-lock.json | 12 +- packages/headless-driver-runner/package.json | 6 +- .../headless-driver-runner/src/Platform.ts | 13 +- packages/headless-driver-runner/src/index.ts | 1 + packages/headless-driver-runner/src/utils.ts | 37 + packages/headless-driver/package.json | 6 +- .../src/__tests__/AMFlowSpec.ts | 449 +++++++++++ .../headless-driver/src/__tests__/PlaySpec.ts | 157 ++++ .../src/__tests__/constants.ts | 19 + .../__tests__/helpers/MockRunnerManager.ts | 27 + .../src/__tests__/helpers/SilentLogger.ts | 16 + .../headless-driver/src/__tests__/runSpec.ts | 731 ++---------------- packages/headless-driver/src/play/Play.ts | 20 +- .../headless-driver/src/play/PlayManager.ts | 10 +- .../src/runner/RunnerManager.ts | 48 +- 22 files changed, 883 insertions(+), 733 deletions(-) create mode 100644 packages/headless-driver-runner/src/utils.ts create mode 100644 packages/headless-driver/src/__tests__/AMFlowSpec.ts create mode 100644 packages/headless-driver/src/__tests__/PlaySpec.ts create mode 100644 packages/headless-driver/src/__tests__/constants.ts create mode 100644 packages/headless-driver/src/__tests__/helpers/MockRunnerManager.ts create mode 100644 packages/headless-driver/src/__tests__/helpers/SilentLogger.ts diff --git a/package-lock.json b/package-lock.json index f617da025..b3f217f7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2781,12 +2781,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2801,17 +2803,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2928,7 +2933,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2940,6 +2946,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2954,6 +2961,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2961,12 +2969,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2985,6 +2995,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3065,7 +3076,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3077,6 +3089,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3198,6 +3211,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/packages/headless-driver-runner-v1/package.json b/packages/headless-driver-runner-v1/package.json index 1918ecd00..d9089b4e4 100644 --- a/packages/headless-driver-runner-v1/package.json +++ b/packages/headless-driver-runner-v1/package.json @@ -22,13 +22,11 @@ "typings": "lib/index.d.ts", "dependencies": { "@akashic/engine-files": "1.1.9", - "@akashic/headless-driver-runner": "0.4.0", - "node-fetch": "2.3.0" + "@akashic/headless-driver-runner": "0.4.0" }, "devDependencies": { - "@types/node-fetch": "2.1.2", "prettier": "^1.17.0", - "rimraf": "2.6.3", + "rimraf": "^2.6.3", "tslint": "^5.16.0", "typescript": "^3.4.5" } diff --git a/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts b/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts index a44caf36b..9140a58ba 100644 --- a/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts +++ b/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts @@ -1,5 +1,5 @@ import { akashicEngine as g } from "@akashic/engine-files"; -import fetch from "node-fetch"; +import { loadFile } from "@akashic/headless-driver-runner"; export class NodeScriptAsset extends g.ScriptAsset { static PRE_SCRIPT: string = "(function(exports, require, module, __filename, __dirname) {\n"; @@ -12,8 +12,7 @@ export class NodeScriptAsset extends g.ScriptAsset { } _load(loader: g.AssetLoadHandler): void { - fetch(this.path, { method: "GET" }) - .then(res => res.text()) + loadFile(this.path, {json: false}) .then(text => { this.script = text; return loader._onAssetLoad(this); diff --git a/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts b/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts index c7a33a01c..5f97f4ce3 100644 --- a/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts +++ b/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts @@ -1,10 +1,9 @@ import { akashicEngine as g } from "@akashic/engine-files"; -import fetch from "node-fetch"; +import { loadFile } from "@akashic/headless-driver-runner"; export class NodeTextAsset extends g.TextAsset { _load(loader: g.AssetLoadHandler): void { - fetch(this.path, { method: "GET" }) - .then(res => res.text()) + loadFile(this.path, {json: false}) .then(text => { this.data = text; return loader._onAssetLoad(this); diff --git a/packages/headless-driver-runner-v2/package.json b/packages/headless-driver-runner-v2/package.json index 60042f8a6..791e1e066 100644 --- a/packages/headless-driver-runner-v2/package.json +++ b/packages/headless-driver-runner-v2/package.json @@ -22,13 +22,11 @@ "typings": "lib/index.d.ts", "dependencies": { "@akashic/engine-files": "2.1.16", - "@akashic/headless-driver-runner": "0.4.0", - "node-fetch": "2.3.0" + "@akashic/headless-driver-runner": "0.4.0" }, "devDependencies": { - "@types/node-fetch": "2.1.2", "prettier": "^1.17.0", - "rimraf": "2.6.3", + "rimraf": "^2.6.3", "tslint": "^5.16.0", "typescript": "^3.4.5" } diff --git a/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts b/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts index a44caf36b..31711d2c5 100644 --- a/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts +++ b/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts @@ -1,5 +1,5 @@ import { akashicEngine as g } from "@akashic/engine-files"; -import fetch from "node-fetch"; +import { loadFile } from "@akashic/headless-driver-runner"; export class NodeScriptAsset extends g.ScriptAsset { static PRE_SCRIPT: string = "(function(exports, require, module, __filename, __dirname) {\n"; @@ -12,13 +12,14 @@ export class NodeScriptAsset extends g.ScriptAsset { } _load(loader: g.AssetLoadHandler): void { - fetch(this.path, { method: "GET" }) - .then(res => res.text()) + loadFile(this.path, {json: false}) .then(text => { this.script = text; return loader._onAssetLoad(this); }) - .catch(e => loader._onAssetError(this, e)); + .catch(e => { + loader._onAssetError(this, e); + }); } execute(execEnv: g.ScriptAssetExecuteEnvironment): any { diff --git a/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts b/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts index c7a33a01c..5f97f4ce3 100644 --- a/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts +++ b/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts @@ -1,10 +1,9 @@ import { akashicEngine as g } from "@akashic/engine-files"; -import fetch from "node-fetch"; +import { loadFile } from "@akashic/headless-driver-runner"; export class NodeTextAsset extends g.TextAsset { _load(loader: g.AssetLoadHandler): void { - fetch(this.path, { method: "GET" }) - .then(res => res.text()) + loadFile(this.path, {json: false}) .then(text => { this.data = text; return loader._onAssetLoad(this); diff --git a/packages/headless-driver-runner/package-lock.json b/packages/headless-driver-runner/package-lock.json index 77ad19c5b..128d7c8cb 100644 --- a/packages/headless-driver-runner/package-lock.json +++ b/packages/headless-driver-runner/package-lock.json @@ -49,9 +49,9 @@ "dev": true }, "@types/node-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.1.2.tgz", - "integrity": "sha512-XroxUzLpKuL+CVkQqXlffRkEPi4Gh3Oui/mWyS7ztKiyqVxiU+h3imCW5I2NQmde5jK+3q++36/Q96cyRWsweg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.3.3.tgz", + "integrity": "sha512-MIplfRxrDTsIbOLGyFqNWTmxho5Fs710Kul35tEcaqkx9He86mGbSCDvILL0LCMfmm+oJ8tDg51crE9+pJGgiQ==", "dev": true, "requires": { "@types/node": "*" @@ -242,9 +242,9 @@ } }, "node-fetch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", - "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.5.0.tgz", + "integrity": "sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw==" }, "once": { "version": "1.4.0", diff --git a/packages/headless-driver-runner/package.json b/packages/headless-driver-runner/package.json index a7b322f6d..268c8dd7d 100644 --- a/packages/headless-driver-runner/package.json +++ b/packages/headless-driver-runner/package.json @@ -23,12 +23,12 @@ "dependencies": { "@akashic/amflow": "0.2.2", "@akashic/trigger": "0.1.5", - "node-fetch": "2.3.0" + "node-fetch": "2.5.0" }, "devDependencies": { - "@types/node-fetch": "2.1.2", + "@types/node-fetch": "2.3.3", "prettier": "^1.16.0", - "rimraf": "2.6.3", + "rimraf": "^2.6.3", "tslint": "^5.16.0", "typescript": "^3.4.5" } diff --git a/packages/headless-driver-runner/src/Platform.ts b/packages/headless-driver-runner/src/Platform.ts index ab7730f73..c25dc5e0d 100644 --- a/packages/headless-driver-runner/src/Platform.ts +++ b/packages/headless-driver-runner/src/Platform.ts @@ -1,5 +1,5 @@ import { AMFlow } from "@akashic/amflow"; -import fetch from "node-fetch"; +import { loadFile } from "./utils"; export interface PlatformParameters { assetBaseUrl: string; @@ -30,13 +30,8 @@ export abstract class Platform { } loadGameConfiguration(url: string, callback: (err: Error, data?: object) => void): void { - fetch(url, { method: "GET" }) - .then(res => res.json()) - .then(data => { - callback(null, data); - }) - .catch(e => { - callback(e); - }); + loadFile(url, { json: true }) + .then(json => callback(null, json)) + .catch(e => callback(e)); } } diff --git a/packages/headless-driver-runner/src/index.ts b/packages/headless-driver-runner/src/index.ts index bf6070108..fa2bbbc3d 100644 --- a/packages/headless-driver-runner/src/index.ts +++ b/packages/headless-driver-runner/src/index.ts @@ -1,3 +1,4 @@ export * from "./Runner"; export * from "./Platform"; export * from "./Looper"; +export * from "./utils"; diff --git a/packages/headless-driver-runner/src/utils.ts b/packages/headless-driver-runner/src/utils.ts new file mode 100644 index 000000000..f54dfd7b2 --- /dev/null +++ b/packages/headless-driver-runner/src/utils.ts @@ -0,0 +1,37 @@ +import { readFileSync } from "fs"; +import fetch from "node-fetch"; +import * as path from "path"; + +export interface ReadFileOption { + encoding?: string; + json?: boolean; +} + +/** + * テキストファイルの読み込みを行う。 + * + * @param url url または path + */ +export async function loadFile(url: string): Promise; + +/** + * ファイルの読み込みを行う。 + * + * @param url url または path + * @param opt オプション + */ +export async function loadFile(url: string, opt?: ReadFileOption): Promise; + +export async function loadFile(url: string, opt?: ReadFileOption): Promise { + if (isHttpProtocol(url)) { + const res = await fetch(url, { method: "GET" }); + return opt.json ? res.json() : res.text(); + } else { + const str = readFileSync(path.resolve(this.assetBaseUrl, url), { encoding: opt.encoding ? opt.encoding : "utf8" }); + return opt.json ? JSON.parse(str) : str; + } +} + +export function isHttpProtocol(url: string): boolean { + return /^(http|https)\:\/\//.test(url); +} diff --git a/packages/headless-driver/package.json b/packages/headless-driver/package.json index fafa042f5..628fe9506 100644 --- a/packages/headless-driver/package.json +++ b/packages/headless-driver/package.json @@ -10,7 +10,7 @@ "prepublish": "npm run build", "build": "rimraf ./lib && tsc -p ./tsconfig.json && npm run formatter", "formatter": "prettier --write \"src/**/*.ts\" --config ../prettier.config.js", - "test": "npm run build && npm run run:jest && npm run lint", + "test": "npm run run:jest && npm run lint", "run:jest": "jest", "lint": "tslint -c ../tslint.json -p tsconfig.json -e \"**/node_modules/**\"" }, @@ -29,14 +29,12 @@ "@akashic/playlog": "1.3.1", "@akashic/trigger": "0.1.5", "js-sha256": "0.9.0", - "lodash.clonedeep": "4.5.0", - "node-fetch": "2.3.0" + "lodash.clonedeep": "4.5.0" }, "devDependencies": { "@types/get-port": "4.0.0", "@types/jest": "24.0.12", "@types/lodash.clonedeep": "4.5.4", - "@types/node-fetch": "2.1.2", "get-port": "^5.0.0", "jest": "^24.8.0", "prettier": "^1.17.0", diff --git a/packages/headless-driver/src/__tests__/AMFlowSpec.ts b/packages/headless-driver/src/__tests__/AMFlowSpec.ts new file mode 100644 index 000000000..70b72c975 --- /dev/null +++ b/packages/headless-driver/src/__tests__/AMFlowSpec.ts @@ -0,0 +1,449 @@ +import { GetStartPointOptions, StartPoint } from "@akashic/amflow"; +import { Event } from "@akashic/playlog"; +import { setSystemLogger } from "../Logger"; +import { AMFlowClient } from "../play/amflow/AMFlowClient"; +import { BadRequestError, PermissionError } from "../play/amflow/ErrorFactory"; +import { AMFlowClientManager } from "../play/AMFlowClientManager"; +import { PlayManager } from "../play/PlayManager"; +import { activePermission, passivePermission } from "./constants"; +import { SilentLogger } from "./helpers/SilentLogger"; + +setSystemLogger(new SilentLogger()); + +describe("AMFlow の動作テスト", () => { + it("getStartPoint で正しく startPoint が取得できる", done => { + const amflowClientManager = new AMFlowClientManager(); + const amflowClient = amflowClientManager.createAMFlow("0"); + amflowClient.open("0", () => { + const token = amflowClientManager.createPlayToken("0", activePermission); + amflowClient.authenticate(token, async () => { + const getStartPoint: (opts: GetStartPointOptions) => Promise = opts => + new Promise((resolve, reject) => { + amflowClient.getStartPoint(opts, (e, data) => (e ? reject(e) : resolve(data))); + }); + const putStartPoint: (sp: StartPoint) => Promise = sp => + new Promise((resolve, reject) => { + amflowClient.putStartPoint(sp, e => (e ? reject(e) : resolve())); + }); + + await putStartPoint({ + frame: 0, + timestamp: 100, + data: "frame0" + }); + await putStartPoint({ + frame: 100, + timestamp: 10000, + data: "frame100" + }); + await putStartPoint({ + frame: 500, + timestamp: 50000, + data: "frame500" + }); + await putStartPoint({ + frame: 200, + timestamp: 20000, + data: "frame200" + }); + + // default: frame === 0 + const frame = await getStartPoint({}); + expect(frame.data).toBe("frame0"); + + // only frame + const frame0 = await getStartPoint({ frame: 0 }); + const frame100 = await getStartPoint({ frame: 100 }); + const frame700 = await getStartPoint({ frame: 700 }); + + expect(frame0.data).toBe("frame0"); + expect(frame100.data).toBe("frame100"); + expect(frame700.data).toBe("frame500"); + + // only timestamp + const timestamp10000 = await getStartPoint({ timestamp: 10000 }); + const timestamp30000 = await getStartPoint({ timestamp: 30000 }); + const timestamp60000 = await getStartPoint({ timestamp: 60000 }); + + expect(timestamp10000.data).toBe("frame100"); + expect(timestamp30000.data).toBe("frame200"); + expect(timestamp60000.data).toBe("frame500"); + + // frame and timestamp + const sp1 = await getStartPoint({ frame: 0, timestamp: 100 }); + const sp2 = await getStartPoint({ frame: 50, timestamp: 500 }); + const sp3 = await getStartPoint({ frame: 100, timestamp: 1000 }); + const sp4 = await getStartPoint({ frame: 1000, timestamp: 10000 }); + + // 内容は関知しないが、エラーが発生しないことを確認 + expect(sp1).not.toBe(null); + expect(sp2).not.toBe(null); + expect(sp3).not.toBe(null); + expect(sp4).not.toBe(null); + + // no startPoint + try { + await getStartPoint({ timestamp: 0 }); + fail("Must throw error"); + } catch (e) { + // no startPoint found + expect(e.message).toBe("No start point"); + } + + done(); + }); + }); + }); + + it("AMFlow#onEvent が登録されるより以前の Event を正しく取得できる", done => { + const playManager = new PlayManager(); + let activeAMFlow: AMFlowClient; + let passiveAMFlow: AMFlowClient; + let playId: string; + const events: Event[] = []; + playManager + .createPlay({ + contentUrl: "dummy" + }) + .then(p => { + return new Promise((resolve, reject) => { + playId = p; + activeAMFlow = playManager.createAMFlow(playId); + activeAMFlow.open(playId, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + passiveAMFlow = playManager.createAMFlow(playId); + passiveAMFlow.open(playId, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // 認証できる + const playToken = playManager.createPlayToken(playId, passivePermission); + passiveAMFlow.authenticate(playToken, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => { + // active の AMFlow#authenticate(), AMFlow#onEvent() 呼び出し前にイベントを送信 + passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 1, hoge: "fuga" }]); + passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 2, foo: "bar" }]); + }) + .then(() => { + return new Promise((resolve, reject) => { + // Active の認証 + const playToken = playManager.createPlayToken(playId, activePermission); + activeAMFlow.authenticate(playToken, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => { + activeAMFlow.onEvent(event => { + events.push(event); + }); + }) + .then(() => { + // active の AMFlow#onEvent() 呼び出し後にイベントを送信 + passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 3 }]); + passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 4 }]); + }) + .then(() => { + expect(events).toEqual([ + [0x20, 0, null, { ordinal: 1, hoge: "fuga" }], + [0x20, 0, null, { ordinal: 2, foo: "bar" }], + [0x20, 0, null, { ordinal: 3 }], + [0x20, 0, null, { ordinal: 4 }] + ]); + }) + .then(done) + .catch(e => done(e)); + }); + + it("AMFlow通信ができる", done => { + const playManager = new PlayManager(); + let playId: string; + let activeAMFlow: AMFlowClient; + let passiveAMFlow: AMFlowClient; + let failureAMFlow: AMFlowClient; + playManager + .createPlay({ + contentUrl: "dummy" + }) + .then(p => { + playId = p; + }) + .then(() => { + return new Promise((resolve, reject) => { + activeAMFlow = playManager.createAMFlow(playId); + activeAMFlow.open(playId, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + passiveAMFlow = playManager.createAMFlow(playId); + passiveAMFlow.open(playId, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + failureAMFlow = playManager.createAMFlow(playId); + failureAMFlow.open(playId, err => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // 認証できない + passiveAMFlow.authenticate("dummy-token", (err, permission) => { + if (err) { + expect(err instanceof Error).toBe(true); + expect(permission).toBe(null); + expect(err.name).toBe("InvalidStatus"); + resolve(); + return; + } + reject(new Error("認証できないはず")); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // 認証できる + const playToken = playManager.createPlayToken(playId, passivePermission); + passiveAMFlow.authenticate(playToken, (err, permission) => { + if (err) { + reject(err); + return; + } + expect(permission).toEqual({ + readTick: true, + writeTick: false, + sendEvent: true, + subscribeEvent: false, + subscribeTick: false, + maxEventPriority: 2 + }); + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // 認証できる + const playToken = playManager.createPlayToken(playId, activePermission); + activeAMFlow.authenticate(playToken, (err, permission) => { + if (err) { + reject(err); + return; + } + expect(permission).toEqual({ + readTick: true, + writeTick: true, + sendEvent: true, + subscribeEvent: true, + subscribeTick: true, + maxEventPriority: 2 + }); + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // Tick を送信できる + activeAMFlow.sendTick([0]); + + // TickList を取得できる + passiveAMFlow.getTickList(0, 1, (err, tickList) => { + if (err) { + reject(err); + return; + } + expect(err).toBe(null); + expect(tickList).toEqual([0, 0, []]); + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // Event の受信ハンドラを登録できる + const eventHandler = (event: number[]) => { + // Max Priority の確認 + expect(event).toEqual([0, 2, "dummy-player-id"]); + activeAMFlow.offEvent(eventHandler); + resolve(); + }; + activeAMFlow.onEvent(eventHandler); + + // Event を送信できる + passiveAMFlow.sendEvent([0, 5, "dummy-player-id"]); + }); + }) + .then(() => { + return playManager.suspendPlay(playId); + }) + .then(() => { + return new Promise((resolve, reject) => { + // suspend 時に write, send 権限を含む permission は認証できない + const playToken = playManager.createPlayToken(playId, activePermission); + failureAMFlow.authenticate(playToken, (err, permission) => { + if (err) { + expect(err instanceof PermissionError).toBe(true); + resolve(); + return; + } + reject(new Error("認証できないはず")); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // Play を suspend した後でも TickList を取得できる + passiveAMFlow.getTickList(0, 1, (err, tickList) => { + if (err) { + reject(err); + return; + } + expect(tickList).toEqual([0, 0, []]); + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // Play を suspend した後に sendTick することはできない + try { + activeAMFlow.sendTick([1]); + reject("Must throw error"); + } catch (e) { + expect(e instanceof BadRequestError).toBe(true); + } + // Play を suspend した後に sendEvent することはできない + try { + passiveAMFlow.sendEvent([0, 1, "dummy-player-id"]); + reject("Must throw error"); + } catch (e) { + expect(e instanceof BadRequestError).toBe(true); + } + // Play を suspend した後に putStartPoint することはできない + activeAMFlow.putStartPoint( + { + frame: 10, + timestamp: 1000, + data: "hoge" + }, + e => { + if (e) { + expect(e instanceof BadRequestError).toBe(true); + resolve(); + } + reject("Must throw error"); + } + ); + }); + }) + .then(() => { + return playManager.resumePlay(playId); + }) + .then(() => { + return new Promise((resolve, reject) => { + // Play を resume した後に sendTick できる + activeAMFlow.sendTick([1]); + // Play を resume した後に sendEvent できる + passiveAMFlow.sendEvent([0, 1, "dummy-player-id"]); + // Play を resume した後に putStartPoint できる + activeAMFlow.putStartPoint( + { + frame: 10, + timestamp: 1000, + data: "hoge" + }, + e => { + if (e) { + reject(e); + return; + } + resolve(); + } + ); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // Play を resume した後に TickList を取得できる + passiveAMFlow.getTickList(0, 1, (err, tickList) => { + if (err) { + reject(err); + return; + } + expect(tickList).toEqual([0, 1, []]); + resolve(); + }); + }); + }) + .then(() => { + return new Promise((resolve, reject) => { + // resume 後に write, send 権限を含む permission が認証できる + const playToken = playManager.createPlayToken(playId, activePermission); + failureAMFlow.authenticate(playToken, (err, permission) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + }) + .then(() => playManager.deletePlay(playId)) + .then(() => { + return new Promise((resolve, reject) => { + // すでに delete したプレーの AMFlowClient に対して close() を呼び出しても問題ない + passiveAMFlow.close(err => (err ? reject(err) : resolve())); + }); + }) + .then(done) + .catch(e => done(e)); + }); +}); diff --git a/packages/headless-driver/src/__tests__/PlaySpec.ts b/packages/headless-driver/src/__tests__/PlaySpec.ts new file mode 100644 index 000000000..b00f183ad --- /dev/null +++ b/packages/headless-driver/src/__tests__/PlaySpec.ts @@ -0,0 +1,157 @@ +import { setSystemLogger } from "../Logger"; +import { AMFlowStore } from "../play/amflow/AMFlowStore"; +import { AMFlowClientManager } from "../play/AMFlowClientManager"; +import { PlayManager } from "../play/PlayManager"; +import { activePermission } from "./constants"; +import { MockRunnerManager } from "./helpers/MockRunnerManager"; +import { SilentLogger } from "./helpers/SilentLogger"; + +setSystemLogger(new SilentLogger()); + +const contentUrl = process.env.CONTENT_URL_V2; + +describe("プレー周りのテスト", () => { + it("各インスタンスを生成できる", async () => { + const playManager = new PlayManager(); + const playId0 = await playManager.createPlay({ + contentUrl + }); + expect(playId0).toBe("0"); + + const amflow0 = playManager.createAMFlow(playId0); + expect(amflow0.playId).toBe("0"); + + const playToken0 = playManager.createPlayToken("0", activePermission); + + const runnerManager = new MockRunnerManager(playManager); + const runnerId0 = await runnerManager.createRunner({ + playId: playId0, + amflow: amflow0, + playToken: playToken0, + executionMode: "active" + }); + const runner0 = runnerManager.getRunner(runnerId0); + + expect(runner0.runnerId).toBe("0"); + expect(runner0.engineVersion).toBe("2"); + + const playId1 = await playManager.createPlay({ + contentUrl + }); + expect(playId1).toBe("1"); + + const amflow1 = playManager.createAMFlow(playId1); + expect(amflow1.playId).toBe("1"); + + const playToken1 = playManager.createPlayToken("1", activePermission); + + const runnerId1 = await runnerManager.createRunner({ + playId: playId1, + amflow: amflow1, + playToken: playToken1, + executionMode: "active" + }); + const runner1 = runnerManager.getRunner(runnerId1); + + expect(runner1.runnerId).toBe("1"); + expect(runner0.engineVersion).toBe("2"); + + await runnerManager.startRunner("0"); + await runnerManager.stopRunner("0"); + expect(runnerManager.getRunner("0")).toBe(null); + + playManager.deletePlay("0"); + expect(playManager.getPlay("0")).toBe(null); + + const playId2 = await playManager.createPlay({ + contentUrl + }); + expect(playId2).toBe("2"); + + const amflow2 = playManager.createAMFlow(playId2); + expect(amflow2.playId).toBe("2"); + + const playToken2 = playManager.createPlayToken("2", activePermission); + + const runnerId2 = await runnerManager.createRunner({ + playId: playId2, + amflow: amflow2, + playToken: playToken2, + executionMode: "active" + }); + const runner2 = runnerManager.getRunner(runnerId2); + expect(runner2.runnerId).toBe("2"); + expect(runner2.engineVersion).toBe("2"); + }); + + it("AMFlow, playTokenの管理ができる", () => { + const amflowClientManager = new AMFlowClientManager(); + + const token1 = amflowClientManager.createPlayToken("0", activePermission); + const authenticated1 = amflowClientManager.authenticatePlayToken("0", token1); + expect(authenticated1).toEqual(activePermission); + expect((amflowClientManager as any).playTokenMap["0"]).not.toBe(null); + + amflowClientManager.createAMFlow("0"); + const storeMap: Map = (amflowClientManager as any).storeMap; + expect(storeMap.get("0")).not.toBe(null); + + const token2 = amflowClientManager.createPlayToken("1", activePermission); + const authenticated2 = amflowClientManager.authenticatePlayToken("1", token2, true); + expect(authenticated2).toEqual(activePermission); + + amflowClientManager.createAMFlow("1"); + expect(storeMap.get("1")).not.toBe(null); + + amflowClientManager.deleteAllPlayTokens("0"); + amflowClientManager.deleteAMFlowStore("0"); + expect((amflowClientManager as any).playTokenMap["0"]).toBeUndefined(); + expect(storeMap.get("0")).toBeUndefined(); + }); + + it("Play の管理ができる", async () => { + const playManager = new PlayManager(); + const playId1 = await playManager.createPlay({ contentUrl }); + const playId2 = await playManager.createPlay({ contentUrl }); + const playId3 = await playManager.createPlay({ contentUrl }); + + let playIds = playManager.getAllPlays().map(play => play.playId); + expect(playIds).toEqual([playId1, playId2, playId3]); + + playManager.suspendPlay(playId1); + + // すべてのPlay + playIds = playManager.getAllPlays().map(play => play.playId); + expect(playIds).toEqual([playId1, playId2, playId3]); + // running の Play + playIds = playManager.getPlays({ status: "running" }).map(play => play.playId); + expect(playIds).toEqual([playId2, playId3]); + // suspend の Play + playIds = playManager.getPlays({ status: "suspending" }).map(play => play.playId); + expect(playIds).toEqual([playId1]); + + playManager.deletePlay(playId2); + + // すべてのPlay + playIds = playManager.getAllPlays().map(play => play.playId); + expect(playIds).toEqual([playId1, playId3]); + // running の Play + playIds = playManager.getPlays({ status: "running" }).map(play => play.playId); + expect(playIds).toEqual([playId3]); + // suspend の Play + playIds = playManager.getPlays({ status: "suspending" }).map(play => play.playId); + expect(playIds).toEqual([playId1]); + + playManager.resumePlay(playId1); + + // すべてのPlay + playIds = playManager.getAllPlays().map(play => play.playId); + expect(playIds).toEqual([playId1, playId3]); + // running の Play + playIds = playManager.getPlays({ status: "running" }).map(play => play.playId); + expect(playIds).toEqual([playId1, playId3]); + // suspend の Play + playIds = playManager.getPlays({ status: "suspending" }).map(play => play.playId); + expect(playIds).toEqual([]); + }); +}); diff --git a/packages/headless-driver/src/__tests__/constants.ts b/packages/headless-driver/src/__tests__/constants.ts new file mode 100644 index 000000000..6180c51e5 --- /dev/null +++ b/packages/headless-driver/src/__tests__/constants.ts @@ -0,0 +1,19 @@ +import { Permission } from "@akashic/amflow"; + +export const activePermission: Permission = { + readTick: true, + writeTick: true, + sendEvent: true, + subscribeEvent: true, + subscribeTick: true, + maxEventPriority: 2 +}; + +export const passivePermission: Permission = { + readTick: true, + writeTick: false, + sendEvent: true, + subscribeEvent: false, + subscribeTick: false, + maxEventPriority: 2 +}; diff --git a/packages/headless-driver/src/__tests__/helpers/MockRunnerManager.ts b/packages/headless-driver/src/__tests__/helpers/MockRunnerManager.ts new file mode 100644 index 000000000..e2da73947 --- /dev/null +++ b/packages/headless-driver/src/__tests__/helpers/MockRunnerManager.ts @@ -0,0 +1,27 @@ +import { loadFile } from "@akashic/headless-driver-runner"; +import { RunnerManager } from "../../runner/RunnerManager"; + +const gameJsonUrlV1 = process.env.GAME_JSON_URL_V1; +const gameJsonUrlV2 = process.env.GAME_JSON_URL_V2; +const assetBaseUrlV1 = process.env.ASSET_BASE_URL_V1; +const assetBaseUrlV2 = process.env.ASSET_BASE_URL_V2; +const cascadeGameJsonUrlV2 = process.env.CASCADE_GAME_JSON_URL_V2; + +export class MockRunnerManager extends RunnerManager { + protected async resolveContent(contentUrl: string): Promise { + const config = await loadFile(contentUrl, { json: true }); + if (config.content_url === "v1_content_url") { + config.content_url = gameJsonUrlV1; + } else if (config.content_url === "v2_content_url") { + config.content_url = gameJsonUrlV2; + } else if (config.content_url === "v2_content_cascade_url") { + config.content_url = cascadeGameJsonUrlV2; + } + if (config.asset_base_url === "v1_asset_base_url") { + config.asset_base_url = assetBaseUrlV1; + } else if (config.asset_base_url === "v2_asset_base_url") { + config.asset_base_url = assetBaseUrlV2; + } + return config; + } +} diff --git a/packages/headless-driver/src/__tests__/helpers/SilentLogger.ts b/packages/headless-driver/src/__tests__/helpers/SilentLogger.ts new file mode 100644 index 000000000..4f2cd68a8 --- /dev/null +++ b/packages/headless-driver/src/__tests__/helpers/SilentLogger.ts @@ -0,0 +1,16 @@ +import { SystemLogger } from "../../Logger"; + +export class SilentLogger implements SystemLogger { + info(...messages: any[]): void { + // console.info(...messages); // tslint:disable-line:no-console + } + debug(...messages: any[]): void { + // console.debug(...messages); // tslint:disable-line:no-console + } + warn(...messages: any[]): void { + // console.warn(...messages); // tslint:disable-line:no-console + } + error(...messages: any[]): void { + // console.error(...messages); // tslint:disable-line:no-console + } +} diff --git a/packages/headless-driver/src/__tests__/runSpec.ts b/packages/headless-driver/src/__tests__/runSpec.ts index 941fa058b..0860f57d9 100644 --- a/packages/headless-driver/src/__tests__/runSpec.ts +++ b/packages/headless-driver/src/__tests__/runSpec.ts @@ -1,670 +1,20 @@ -import { GetStartPointOptions, Permission, StartPoint } from "@akashic/amflow"; import { RunnerV1, RunnerV1Game } from "@akashic/headless-driver-runner-v1"; import { RunnerV2, RunnerV2Game } from "@akashic/headless-driver-runner-v2"; -import { Event } from "@akashic/playlog"; -import fetch from "node-fetch"; -import { setSystemLogger, SystemLogger } from "../Logger"; -import { AMFlowClient } from "../play/amflow/AMFlowClient"; -import { AMFlowStore } from "../play/amflow/AMFlowStore"; -import { BadRequestError, PermissionError } from "../play/amflow/ErrorFactory"; -import { AMFlowClientManager } from "../play/AMFlowClientManager"; +import * as path from "path"; +import { setSystemLogger } from "../Logger"; import { PlayManager } from "../play/PlayManager"; import { RunnerManager } from "../runner/RunnerManager"; +import { activePermission, passivePermission } from "./constants"; +import { MockRunnerManager } from "./helpers/MockRunnerManager"; +import { SilentLogger } from "./helpers/SilentLogger"; const contentUrlV1 = process.env.CONTENT_URL_V1; const contentUrlV2 = process.env.CONTENT_URL_V2; -const gameJsonUrlV1 = process.env.GAME_JSON_URL_V1; -const gameJsonUrlV2 = process.env.GAME_JSON_URL_V2; -const assetBaseUrlV1 = process.env.ASSET_BASE_URL_V1; -const assetBaseUrlV2 = process.env.ASSET_BASE_URL_V2; const cascadeContentUrlV2 = process.env.CASCADE_CONTENT_URL_V2; -const cascadeGameJsonUrlV2 = process.env.CASCADE_GAME_JSON_URL_V2; - -const activePermission: Permission = { - readTick: true, - writeTick: true, - sendEvent: true, - subscribeEvent: true, - subscribeTick: true, - maxEventPriority: 2 -}; - -const passivePermission: Permission = { - readTick: true, - writeTick: false, - sendEvent: true, - subscribeEvent: false, - subscribeTick: false, - maxEventPriority: 2 -}; - -class SilentLogger implements SystemLogger { - info(...messages: any[]): void { - // - } - debug(...messages: any[]): void { - // - } - warn(...messages: any[]): void { - // - } - error(...messages: any[]): void { - // - } -} setSystemLogger(new SilentLogger()); -class MockRunnerManager extends RunnerManager { - protected fetchContentUrl(contentUrl: string): Promise { - return new Promise((resolve, reject) => { - fetch(contentUrl, { method: "GET" }) - .then(res => res.json()) - .then((config: any) => { - if (config.content_url === "v1_content_url") { - config.content_url = gameJsonUrlV1; - } else if (config.content_url === "v2_content_url") { - config.content_url = gameJsonUrlV2; - } else if (config.content_url === "v2_content_cascade_url") { - config.content_url = cascadeGameJsonUrlV2; - } - if (config.asset_base_url === "v1_asset_base_url") { - config.asset_base_url = assetBaseUrlV1; - } else if (config.asset_base_url === "v2_asset_base_url") { - config.asset_base_url = assetBaseUrlV2; - } - resolve(config); - }) - .catch(e => reject(e)); - }); - } -} - -describe("run-test", () => { - it("各インスタンスを生成できる", async () => { - const playManager = new PlayManager(); - const playId0 = await playManager.createPlay({ - contentUrl: contentUrlV2 - }); - expect(playId0).toBe("0"); - - const amflow0 = playManager.createAMFlow(playId0); - expect(amflow0.playId).toBe("0"); - - const playToken0 = playManager.createPlayToken("0", activePermission); - - const runnerManager = new MockRunnerManager(playManager); - const runnerId0 = await runnerManager.createRunner({ - playId: playId0, - amflow: amflow0, - playToken: playToken0, - executionMode: "active" - }); - const runner0 = runnerManager.getRunner(runnerId0); - - expect(runner0.runnerId).toBe("0"); - expect(runner0.engineVersion).toBe("2"); - - const playId1 = await playManager.createPlay({ - contentUrl: contentUrlV2 - }); - expect(playId1).toBe("1"); - - const amflow1 = playManager.createAMFlow(playId1); - expect(amflow1.playId).toBe("1"); - - const playToken1 = playManager.createPlayToken("1", activePermission); - - const runnerId1 = await runnerManager.createRunner({ - playId: playId1, - amflow: amflow1, - playToken: playToken1, - executionMode: "active" - }); - const runner1 = runnerManager.getRunner(runnerId1); - - expect(runner1.runnerId).toBe("1"); - expect(runner0.engineVersion).toBe("2"); - - await runnerManager.startRunner("0"); - await runnerManager.stopRunner("0"); - expect(runnerManager.getRunner("0")).toBe(null); - - playManager.deletePlay("0"); - expect(playManager.getPlay("0")).toBe(null); - - const playId2 = await playManager.createPlay({ - contentUrl: contentUrlV2 - }); - expect(playId2).toBe("2"); - - const amflow2 = playManager.createAMFlow(playId2); - expect(amflow2.playId).toBe("2"); - - const playToken2 = playManager.createPlayToken("2", activePermission); - - const runnerId2 = await runnerManager.createRunner({ - playId: playId2, - amflow: amflow2, - playToken: playToken2, - executionMode: "active" - }); - const runner2 = runnerManager.getRunner(runnerId2); - expect(runner2.runnerId).toBe("2"); - expect(runner2.engineVersion).toBe("2"); - }); - - it("AMFlow, playTokenの管理ができる", () => { - const amflowClientManager = new AMFlowClientManager(); - - const token1 = amflowClientManager.createPlayToken("0", activePermission); - const authenticated1 = amflowClientManager.authenticatePlayToken("0", token1); - expect(authenticated1).toEqual(activePermission); - expect((amflowClientManager as any).playTokenMap["0"]).not.toBe(null); - - amflowClientManager.createAMFlow("0"); - const storeMap: Map = (amflowClientManager as any).storeMap; - expect(storeMap.get("0")).not.toBe(null); - - const token2 = amflowClientManager.createPlayToken("1", activePermission); - const authenticated2 = amflowClientManager.authenticatePlayToken("1", token2, true); - expect(authenticated2).toEqual(activePermission); - - amflowClientManager.createAMFlow("1"); - expect(storeMap.get("1")).not.toBe(null); - - amflowClientManager.deleteAllPlayTokens("0"); - amflowClientManager.deleteAMFlowStore("0"); - expect((amflowClientManager as any).playTokenMap["0"]).toBeUndefined(); - expect(storeMap.get("0")).toBeUndefined(); - }); - - it("Play の管理ができる", async () => { - const playManager = new PlayManager(); - const playId1 = await playManager.createPlay({ contentUrl: contentUrlV2 }); - const playId2 = await playManager.createPlay({ contentUrl: contentUrlV2 }); - const playId3 = await playManager.createPlay({ contentUrl: contentUrlV2 }); - - let playIds = playManager.getAllPlays().map(play => play.playId); - expect(playIds).toEqual([playId1, playId2, playId3]); - - playManager.suspendPlay(playId1); - - // すべてのPlay - playIds = playManager.getAllPlays().map(play => play.playId); - expect(playIds).toEqual([playId1, playId2, playId3]); - // running の Play - playIds = playManager.getPlays({ status: "running" }).map(play => play.playId); - expect(playIds).toEqual([playId2, playId3]); - // suspend の Play - playIds = playManager.getPlays({ status: "suspending" }).map(play => play.playId); - expect(playIds).toEqual([playId1]); - - playManager.deletePlay(playId2); - - // すべてのPlay - playIds = playManager.getAllPlays().map(play => play.playId); - expect(playIds).toEqual([playId1, playId3]); - // running の Play - playIds = playManager.getPlays({ status: "running" }).map(play => play.playId); - expect(playIds).toEqual([playId3]); - // suspend の Play - playIds = playManager.getPlays({ status: "suspending" }).map(play => play.playId); - expect(playIds).toEqual([playId1]); - - playManager.resumePlay(playId1); - - // すべてのPlay - playIds = playManager.getAllPlays().map(play => play.playId); - expect(playIds).toEqual([playId1, playId3]); - // running の Play - playIds = playManager.getPlays({ status: "running" }).map(play => play.playId); - expect(playIds).toEqual([playId1, playId3]); - // suspend の Play - playIds = playManager.getPlays({ status: "suspending" }).map(play => play.playId); - expect(playIds).toEqual([]); - }); - - it("AMFlow通信ができる", done => { - const playManager = new PlayManager(); - let playId: string; - let activeAMFlow: AMFlowClient; - let passiveAMFlow: AMFlowClient; - let failureAMFlow: AMFlowClient; - playManager - .createPlay({ - contentUrl: contentUrlV2 - }) - .then(p => { - playId = p; - }) - .then(() => { - return new Promise((resolve, reject) => { - activeAMFlow = playManager.createAMFlow(playId); - activeAMFlow.open(playId, err => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - passiveAMFlow = playManager.createAMFlow(playId); - passiveAMFlow.open(playId, err => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - failureAMFlow = playManager.createAMFlow(playId); - failureAMFlow.open(playId, err => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // 認証できない - passiveAMFlow.authenticate("dummy-token", (err, permission) => { - if (err) { - expect(err instanceof Error).toBe(true); - expect(permission).toBe(null); - expect(err.name).toBe("InvalidStatus"); - resolve(); - return; - } - reject(new Error("認証できないはず")); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // 認証できる - const playToken = playManager.createPlayToken(playId, passivePermission); - passiveAMFlow.authenticate(playToken, (err, permission) => { - if (err) { - reject(err); - return; - } - expect(permission).toEqual({ - readTick: true, - writeTick: false, - sendEvent: true, - subscribeEvent: false, - subscribeTick: false, - maxEventPriority: 2 - }); - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // 認証できる - const playToken = playManager.createPlayToken(playId, activePermission); - activeAMFlow.authenticate(playToken, (err, permission) => { - if (err) { - reject(err); - return; - } - expect(permission).toEqual({ - readTick: true, - writeTick: true, - sendEvent: true, - subscribeEvent: true, - subscribeTick: true, - maxEventPriority: 2 - }); - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // Tick を送信できる - activeAMFlow.sendTick([0]); - - // TickList を取得できる - passiveAMFlow.getTickList(0, 1, (err, tickList) => { - if (err) { - reject(err); - return; - } - expect(err).toBe(null); - expect(tickList).toEqual([0, 0, []]); - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // Event の受信ハンドラを登録できる - const eventHandler = (event: number[]) => { - // Max Priority の確認 - expect(event).toEqual([0, 2, "dummy-player-id"]); - activeAMFlow.offEvent(eventHandler); - resolve(); - }; - activeAMFlow.onEvent(eventHandler); - - // Event を送信できる - passiveAMFlow.sendEvent([0, 5, "dummy-player-id"]); - }); - }) - .then(() => { - return playManager.suspendPlay(playId); - }) - .then(() => { - return new Promise((resolve, reject) => { - // suspend 時に write, send 権限を含む permission は認証できない - const playToken = playManager.createPlayToken(playId, activePermission); - failureAMFlow.authenticate(playToken, (err, permission) => { - if (err) { - expect(err instanceof PermissionError).toBe(true); - resolve(); - return; - } - reject(new Error("認証できないはず")); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // Play を suspend した後でも TickList を取得できる - passiveAMFlow.getTickList(0, 1, (err, tickList) => { - if (err) { - reject(err); - return; - } - expect(tickList).toEqual([0, 0, []]); - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // Play を suspend した後に sendTick することはできない - try { - activeAMFlow.sendTick([1]); - reject("Must throw error"); - } catch (e) { - expect(e instanceof BadRequestError).toBe(true); - } - // Play を suspend した後に sendEvent することはできない - try { - passiveAMFlow.sendEvent([0, 1, "dummy-player-id"]); - reject("Must throw error"); - } catch (e) { - expect(e instanceof BadRequestError).toBe(true); - } - // Play を suspend した後に putStartPoint することはできない - activeAMFlow.putStartPoint( - { - frame: 10, - timestamp: 1000, - data: "hoge" - }, - e => { - if (e) { - expect(e instanceof BadRequestError).toBe(true); - resolve(); - } - reject("Must throw error"); - } - ); - }); - }) - .then(() => { - return playManager.resumePlay(playId); - }) - .then(() => { - return new Promise((resolve, reject) => { - // Play を resume した後に sendTick できる - activeAMFlow.sendTick([1]); - // Play を resume した後に sendEvent できる - passiveAMFlow.sendEvent([0, 1, "dummy-player-id"]); - // Play を resume した後に putStartPoint できる - activeAMFlow.putStartPoint( - { - frame: 10, - timestamp: 1000, - data: "hoge" - }, - e => { - if (e) { - reject(e); - return; - } - resolve(); - } - ); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // Play を resume した後に TickList を取得できる - passiveAMFlow.getTickList(0, 1, (err, tickList) => { - if (err) { - reject(err); - return; - } - expect(tickList).toEqual([0, 1, []]); - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // resume 後に write, send 権限を含む permission が認証できる - const playToken = playManager.createPlayToken(playId, activePermission); - failureAMFlow.authenticate(playToken, (err, permission) => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => playManager.deletePlay(playId)) - .then(() => { - return new Promise((resolve, reject) => { - // すでに delete したプレーの AMFlowClient に対して close() を呼び出しても問題ない - passiveAMFlow.close(err => (err ? reject(err) : resolve())); - }); - }) - .then(done) - .catch(e => done(e)); - }); -}); - -describe("AMFlow の動作テスト", () => { - it("getStartPoint で正しく startPoint が取得できる", done => { - const amflowClientManager = new AMFlowClientManager(); - const amflowClient = amflowClientManager.createAMFlow("0"); - amflowClient.open("0", () => { - const token = amflowClientManager.createPlayToken("0", activePermission); - amflowClient.authenticate(token, async () => { - const getStartPoint: (opts: GetStartPointOptions) => Promise = opts => - new Promise((resolve, reject) => { - amflowClient.getStartPoint(opts, (e, data) => (e ? reject(e) : resolve(data))); - }); - const putStartPoint: (sp: StartPoint) => Promise = sp => - new Promise((resolve, reject) => { - amflowClient.putStartPoint(sp, e => (e ? reject(e) : resolve())); - }); - - await putStartPoint({ - frame: 0, - timestamp: 100, - data: "frame0" - }); - await putStartPoint({ - frame: 100, - timestamp: 10000, - data: "frame100" - }); - await putStartPoint({ - frame: 500, - timestamp: 50000, - data: "frame500" - }); - await putStartPoint({ - frame: 200, - timestamp: 20000, - data: "frame200" - }); - - // default: frame === 0 - const frame = await getStartPoint({}); - expect(frame.data).toBe("frame0"); - - // only frame - const frame0 = await getStartPoint({ frame: 0 }); - const frame100 = await getStartPoint({ frame: 100 }); - const frame700 = await getStartPoint({ frame: 700 }); - - expect(frame0.data).toBe("frame0"); - expect(frame100.data).toBe("frame100"); - expect(frame700.data).toBe("frame500"); - - // only timestamp - const timestamp10000 = await getStartPoint({ timestamp: 10000 }); - const timestamp30000 = await getStartPoint({ timestamp: 30000 }); - const timestamp60000 = await getStartPoint({ timestamp: 60000 }); - - expect(timestamp10000.data).toBe("frame100"); - expect(timestamp30000.data).toBe("frame200"); - expect(timestamp60000.data).toBe("frame500"); - - // frame and timestamp - const sp1 = await getStartPoint({ frame: 0, timestamp: 100 }); - const sp2 = await getStartPoint({ frame: 50, timestamp: 500 }); - const sp3 = await getStartPoint({ frame: 100, timestamp: 1000 }); - const sp4 = await getStartPoint({ frame: 1000, timestamp: 10000 }); - - // 内容は関知しないが、エラーが発生しないことを確認 - expect(sp1).not.toBe(null); - expect(sp2).not.toBe(null); - expect(sp3).not.toBe(null); - expect(sp4).not.toBe(null); - - // no startPoint - try { - await getStartPoint({ timestamp: 0 }); - fail("Must throw error"); - } catch (e) { - // no startPoint found - expect(e.message).toBe("No start point"); - } - - done(); - }); - }); - }); - - it("AMFlow#onEvent が登録されるより以前の Event を正しく取得できる", done => { - const playManager = new PlayManager(); - let activeAMFlow: AMFlowClient; - let passiveAMFlow: AMFlowClient; - let playId: string; - const events: Event[] = []; - playManager - .createPlay({ - contentUrl: contentUrlV2 - }) - .then(p => { - return new Promise((resolve, reject) => { - playId = p; - activeAMFlow = playManager.createAMFlow(playId); - activeAMFlow.open(playId, err => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - passiveAMFlow = playManager.createAMFlow(playId); - passiveAMFlow.open(playId, err => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => { - return new Promise((resolve, reject) => { - // 認証できる - const playToken = playManager.createPlayToken(playId, passivePermission); - passiveAMFlow.authenticate(playToken, err => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => { - // active の AMFlow#authenticate(), AMFlow#onEvent() 呼び出し前にイベントを送信 - passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 1, hoge: "fuga" }]); - passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 2, foo: "bar" }]); - }) - .then(() => { - return new Promise((resolve, reject) => { - // Active の認証 - const playToken = playManager.createPlayToken(playId, activePermission); - activeAMFlow.authenticate(playToken, err => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }) - .then(() => { - activeAMFlow.onEvent(event => { - events.push(event); - }); - }) - .then(() => { - // active の AMFlow#onEvent() 呼び出し後にイベントを送信 - passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 3 }]); - passiveAMFlow.sendEvent([0x20, 0, null, { ordinal: 4 }]); - }) - .then(() => { - expect(events).toEqual([ - [0x20, 0, null, { ordinal: 1, hoge: "fuga" }], - [0x20, 0, null, { ordinal: 2, foo: "bar" }], - [0x20, 0, null, { ordinal: 3 }], - [0x20, 0, null, { ordinal: 4 }] - ]); - }) - .then(done) - .catch(e => done(e)); - }); -}); - -describe("コンテンツ動作テスト", () => { +describe("ホスティングされたコンテンツの動作テスト", () => { it("Akashic V1 のコンテンツが動作できる", async () => { const playManager = new PlayManager(); const playId = await playManager.createPlay({ @@ -863,6 +213,75 @@ describe("コンテンツ動作テスト", () => { }); }); +describe("ローカルコンテンツの動作テスト", () => { + it("ローカルの game.json から V1 コンテンツを起動できる", async () => { + const playManager = new PlayManager(); + const playId = await playManager.createPlay({ + contentDir: path.resolve(__dirname, "fixtures", "content-v1"), + contentConfig: {} + }); + const activeAMFlow = playManager.createAMFlow(playId); + const playToken = playManager.createPlayToken(playId, activePermission); + const runnerManager = new RunnerManager(playManager); + const runnerId = await runnerManager.createRunner({ + playId, + amflow: activeAMFlow, + playToken, + executionMode: "active" + }); + const runner = runnerManager.getRunner(runnerId) as RunnerV2; + + const handleData = () => + new Promise((resolve, reject) => { + // コンテンツ側での g.Game#external.send() を捕捉できる + runner.sendToExternalTrigger.handle((l: any) => { + resolve(l); + return true; + }); + }); + + try { + await runner.start(); + const data = await handleData(); + expect(data).toBe("reached right"); + } finally { + runner.stop(); + } + }); + it("ローカルの game.json から V2 コンテンツを起動できる", async () => { + const playManager = new PlayManager(); + const playId = await playManager.createPlay({ + contentDir: path.resolve(__dirname, "fixtures", "content-v2"), + contentConfig: { + externals: ["hoge"] + } + }); + const activeAMFlow = playManager.createAMFlow(playId); + const playToken = playManager.createPlayToken(playId, activePermission); + const runnerManager = new RunnerManager(playManager); + const runnerId = await runnerManager.createRunner({ + playId, + amflow: activeAMFlow, + playToken, + executionMode: "active" + }); + const runner = runnerManager.getRunner(runnerId) as RunnerV2; + + const handleData = () => + new Promise((resolve, reject) => { + // コンテンツ側での g.Game#external.send() を捕捉できる + runner.sendToExternalTrigger.addOnce(l => { + resolve(l); + }); + }); + + await runner.start(); + const data = await handleData(); + expect(data).toBe("reached right"); + runner.stop(); + }); +}); + describe("コンテンツ動作テスト: 異常系", () => { it("存在しない Content URL を指定すると Runner 起動に失敗する", async () => { const playManager = new PlayManager(); diff --git a/packages/headless-driver/src/play/Play.ts b/packages/headless-driver/src/play/Play.ts index e6211f817..07d22e7ea 100644 --- a/packages/headless-driver/src/play/Play.ts +++ b/packages/headless-driver/src/play/Play.ts @@ -1,9 +1,25 @@ export type PlayStatus = "preparing" | "running" | "suspending" | "broken"; -export interface Play { +export type Play = (PlayWithContentUrl | PlayWithContentDir) & BasePlay; + +export interface BasePlay { playId: string; status: PlayStatus; - contentUrl: string; createdAt: number; lastSuspendedAt: number | null; } + +export interface PlayWithContentUrl { + contentUrl: string; +} + +export interface PlayWithContentDir { + /** + * game.json を含むディレクトリ。 + */ + contentDir: string; + contentConfig?: { + externals?: string[]; + assetBasePath?: string; + }; +} diff --git a/packages/headless-driver/src/play/PlayManager.ts b/packages/headless-driver/src/play/PlayManager.ts index cd49199ce..daf2e4fde 100644 --- a/packages/headless-driver/src/play/PlayManager.ts +++ b/packages/headless-driver/src/play/PlayManager.ts @@ -1,11 +1,9 @@ import { Permission } from "@akashic/amflow"; import { AMFlowClient } from "./amflow/AMFlowClient"; import { AMFlowClientManager } from "./AMFlowClientManager"; -import { Play, PlayStatus } from "./Play"; +import { Play, PlayStatus, PlayWithContentDir, PlayWithContentUrl } from "./Play"; -export interface PlayManagerParameters { - contentUrl: string; -} +export type PlayManagerParameters = PlayWithContentUrl | PlayWithContentDir; export interface PlayFilter { status: PlayStatus; @@ -28,9 +26,9 @@ export class PlayManager { this.plays.push({ playId, status: "running", - contentUrl: params.contentUrl, createdAt: Date.now(), - lastSuspendedAt: null + lastSuspendedAt: null, + ...params }); return playId; } diff --git a/packages/headless-driver/src/runner/RunnerManager.ts b/packages/headless-driver/src/runner/RunnerManager.ts index 15b174095..4020b7fb1 100644 --- a/packages/headless-driver/src/runner/RunnerManager.ts +++ b/packages/headless-driver/src/runner/RunnerManager.ts @@ -1,7 +1,7 @@ -import { RunnerExecutionMode, RunnerPlayer } from "@akashic/headless-driver-runner"; +import { loadFile, RunnerExecutionMode, RunnerPlayer } from "@akashic/headless-driver-runner"; import { RunnerV1, RunnerV1Game } from "@akashic/headless-driver-runner-v1"; import { RunnerV2, RunnerV2Game } from "@akashic/headless-driver-runner-v2"; -import fetch from "node-fetch"; +import * as path from "path"; import * as url from "url"; import { getSystemLogger } from "../Logger"; import { AMFlowClient } from "../play/amflow/AMFlowClient"; @@ -55,11 +55,26 @@ export class RunnerManager { } try { - const contentUrl = play.contentUrl; + let engineConfiguration: EngineConfiguration; + let gameConfiguration: GameConfiguration; + let contentUrl: string; + if ("contentUrl" in play) { + contentUrl = play.contentUrl; + engineConfiguration = await this.resolveContent(contentUrl); + gameConfiguration = await this.resolveGameConfiguration(engineConfiguration.content_url); + } else { + contentUrl = path.resolve(play.contentDir, "game.json"); + const config = play.contentConfig; + engineConfiguration = { + external: config != null && config.externals != null ? config.externals : [], + content_url: contentUrl, + asset_base_url: play.contentDir, + engine_urls: [] + }; + gameConfiguration = await this.resolveGameConfiguration(contentUrl); + } const amflow = params.amflow; - const engineConfiguration = await this.fetchContentUrl(play.contentUrl); - const gameConfiguration = await this.loadJson(engineConfiguration.content_url); let configurationBaseUrl: string | null = null; let version: "1" | "2" = "1"; @@ -69,7 +84,7 @@ export class RunnerManager { const defs: GameConfiguration[] = []; for (let i = 0; i < gameConfiguration.definitions.length; i++) { const _url = url.resolve(engineConfiguration.asset_base_url, gameConfiguration.definitions[i]); - const _def = await this.loadJson(_url); + const _def = await this.loadJSON(_url); defs.push(_def); } version = defs.reduce((acc, def) => (def.environment && def.environment["sandbox-runtime"]) || acc, version); @@ -173,20 +188,15 @@ export class RunnerManager { return this.runners; } - protected fetchContentUrl(contentUrl: string): Promise { - return new Promise((resolve, reject) => { - fetch(contentUrl, { method: "GET" }) - .then(res => res.json()) - .then((config: EngineConfiguration) => resolve(config)) - .catch(e => reject(e)); - }); + protected async resolveContent(contentUrl: string): Promise { + return await this.loadJSON(contentUrl); + } + + protected async resolveGameConfiguration(gameJsonUrl: string): Promise { + return await this.loadJSON(gameJsonUrl); } - protected loadJson(jsonUrl: string): Promise { - return new Promise((resolve, reject) => { - fetch(jsonUrl, { method: "GET" }) - .then(res => resolve(res.json())) - .catch(e => reject(e)); - }); + protected async loadJSON(contentUrl: string): Promise { + return await loadFile(contentUrl, { json: true }); } } From d41db0d011057aef8693419b1368e131047e158d Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Mon, 13 May 2019 11:18:54 +0900 Subject: [PATCH 02/10] Fix prettier format --- .../src/platform/assets/NodeScriptAsset.ts | 2 +- .../src/platform/assets/NodeTextAsset.ts | 2 +- .../src/platform/assets/NodeScriptAsset.ts | 2 +- .../src/platform/assets/NodeTextAsset.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts b/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts index 9140a58ba..058a71147 100644 --- a/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts +++ b/packages/headless-driver-runner-v1/src/platform/assets/NodeScriptAsset.ts @@ -12,7 +12,7 @@ export class NodeScriptAsset extends g.ScriptAsset { } _load(loader: g.AssetLoadHandler): void { - loadFile(this.path, {json: false}) + loadFile(this.path, { json: false }) .then(text => { this.script = text; return loader._onAssetLoad(this); diff --git a/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts b/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts index 5f97f4ce3..db17493e6 100644 --- a/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts +++ b/packages/headless-driver-runner-v1/src/platform/assets/NodeTextAsset.ts @@ -3,7 +3,7 @@ import { loadFile } from "@akashic/headless-driver-runner"; export class NodeTextAsset extends g.TextAsset { _load(loader: g.AssetLoadHandler): void { - loadFile(this.path, {json: false}) + loadFile(this.path, { json: false }) .then(text => { this.data = text; return loader._onAssetLoad(this); diff --git a/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts b/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts index 31711d2c5..5d5554ac5 100644 --- a/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts +++ b/packages/headless-driver-runner-v2/src/platform/assets/NodeScriptAsset.ts @@ -12,7 +12,7 @@ export class NodeScriptAsset extends g.ScriptAsset { } _load(loader: g.AssetLoadHandler): void { - loadFile(this.path, {json: false}) + loadFile(this.path, { json: false }) .then(text => { this.script = text; return loader._onAssetLoad(this); diff --git a/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts b/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts index 5f97f4ce3..db17493e6 100644 --- a/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts +++ b/packages/headless-driver-runner-v2/src/platform/assets/NodeTextAsset.ts @@ -3,7 +3,7 @@ import { loadFile } from "@akashic/headless-driver-runner"; export class NodeTextAsset extends g.TextAsset { _load(loader: g.AssetLoadHandler): void { - loadFile(this.path, {json: false}) + loadFile(this.path, { json: false }) .then(text => { this.data = text; return loader._onAssetLoad(this); From 98b2046f71419aa38bbe1cf69627f5141bff1902 Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Mon, 13 May 2019 11:27:56 +0900 Subject: [PATCH 03/10] Update package-lock.json --- package-lock.json | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3f217f7c..f617da025 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2781,14 +2781,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2803,20 +2801,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2933,8 +2928,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2946,7 +2940,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2961,7 +2954,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2969,14 +2961,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2995,7 +2985,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3076,8 +3065,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3089,7 +3077,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3211,7 +3198,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", From d57d0d86036574f927511e3997a94d6e633679fe Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Mon, 13 May 2019 15:07:33 +0900 Subject: [PATCH 04/10] Remove assetBase --- packages/headless-driver-runner/src/utils.ts | 3 +-- packages/headless-driver/src/play/Play.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/headless-driver-runner/src/utils.ts b/packages/headless-driver-runner/src/utils.ts index f54dfd7b2..7e03c78c8 100644 --- a/packages/headless-driver-runner/src/utils.ts +++ b/packages/headless-driver-runner/src/utils.ts @@ -1,6 +1,5 @@ import { readFileSync } from "fs"; import fetch from "node-fetch"; -import * as path from "path"; export interface ReadFileOption { encoding?: string; @@ -27,7 +26,7 @@ export async function loadFile(url: string, opt?: ReadFileOption): Promise const res = await fetch(url, { method: "GET" }); return opt.json ? res.json() : res.text(); } else { - const str = readFileSync(path.resolve(this.assetBaseUrl, url), { encoding: opt.encoding ? opt.encoding : "utf8" }); + const str = readFileSync(url, { encoding: opt.encoding ? opt.encoding : "utf8" }); return opt.json ? JSON.parse(str) : str; } } diff --git a/packages/headless-driver/src/play/Play.ts b/packages/headless-driver/src/play/Play.ts index 07d22e7ea..a1e6ba739 100644 --- a/packages/headless-driver/src/play/Play.ts +++ b/packages/headless-driver/src/play/Play.ts @@ -20,6 +20,5 @@ export interface PlayWithContentDir { contentDir: string; contentConfig?: { externals?: string[]; - assetBasePath?: string; }; } From 04d10390df4894f0f276808eebb4fdf94b2ef569 Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Mon, 13 May 2019 16:36:21 +0900 Subject: [PATCH 05/10] Resolve externals from game.json --- packages/headless-driver-runner/src/Runner.ts | 5 +++++ .../fixtures/content-v2/content.json | 2 +- .../__tests__/fixtures/content-v2/game.json | 5 ++++- .../headless-driver/src/__tests__/runSpec.ts | 12 ++++++------ packages/headless-driver/src/play/Play.ts | 3 --- .../src/runner/RunnerManager.ts | 19 ++++++++++++++++--- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/headless-driver-runner/src/Runner.ts b/packages/headless-driver-runner/src/Runner.ts index 95790a73f..fbf618cbc 100644 --- a/packages/headless-driver-runner/src/Runner.ts +++ b/packages/headless-driver-runner/src/Runner.ts @@ -11,6 +11,7 @@ export interface RunnerParameters { runnerId: string; amflow: AMFlow; executionMode: RunnerExecutionMode; + external?: { [key: string]: string }; gameArgs?: any; player?: RunnerPlayer; } @@ -73,6 +74,10 @@ export abstract class Runner { return this.params.player; } + get external(): { [key: string]: string } | undefined { + return this.params.external; + } + constructor(params: RunnerParameters) { this.params = params; } diff --git a/packages/headless-driver/src/__tests__/fixtures/content-v2/content.json b/packages/headless-driver/src/__tests__/fixtures/content-v2/content.json index 462bdc41d..7aa36b13b 100644 --- a/packages/headless-driver/src/__tests__/fixtures/content-v2/content.json +++ b/packages/headless-driver/src/__tests__/fixtures/content-v2/content.json @@ -3,5 +3,5 @@ "engine_urls": [], "content_url": "v2_content_url", "asset_base_url": "v2_asset_base_url", - "external": [] + "external": ["ext"] } diff --git a/packages/headless-driver/src/__tests__/fixtures/content-v2/game.json b/packages/headless-driver/src/__tests__/fixtures/content-v2/game.json index 3c9487581..3b9617973 100644 --- a/packages/headless-driver/src/__tests__/fixtures/content-v2/game.json +++ b/packages/headless-driver/src/__tests__/fixtures/content-v2/game.json @@ -11,6 +11,9 @@ } }, "environment": { - "sandbox-runtime": "2" + "sandbox-runtime": "2", + "external": { + "ext": "0" + } } } \ No newline at end of file diff --git a/packages/headless-driver/src/__tests__/runSpec.ts b/packages/headless-driver/src/__tests__/runSpec.ts index 0860f57d9..f573610f3 100644 --- a/packages/headless-driver/src/__tests__/runSpec.ts +++ b/packages/headless-driver/src/__tests__/runSpec.ts @@ -36,6 +36,7 @@ describe("ホスティングされたコンテンツの動作テスト", () => { const runner = runnerManager.getRunner(runnerId) as RunnerV1; expect(runner.runnerId).toBe("0"); expect(runner.engineVersion).toBe("1"); + expect(runner.external).toEqual({}); const game = (await runnerManager.startRunner(runner.runnerId)) as RunnerV1Game; expect(game.playId).toBe(playId); @@ -93,6 +94,7 @@ describe("ホスティングされたコンテンツの動作テスト", () => { const runner = runnerManager.getRunner(runnerId) as RunnerV2; expect(runner.runnerId).toBe("0"); expect(runner.engineVersion).toBe("2"); + expect(runner.external).toEqual({ext: "0"}); const game = (await runnerManager.startRunner(runner.runnerId)) as RunnerV2Game; expect(game.playId).toBe(playId); @@ -217,8 +219,7 @@ describe("ローカルコンテンツの動作テスト", () => { it("ローカルの game.json から V1 コンテンツを起動できる", async () => { const playManager = new PlayManager(); const playId = await playManager.createPlay({ - contentDir: path.resolve(__dirname, "fixtures", "content-v1"), - contentConfig: {} + contentDir: path.resolve(__dirname, "fixtures", "content-v1") }); const activeAMFlow = playManager.createAMFlow(playId); const playToken = playManager.createPlayToken(playId, activePermission); @@ -230,6 +231,7 @@ describe("ローカルコンテンツの動作テスト", () => { executionMode: "active" }); const runner = runnerManager.getRunner(runnerId) as RunnerV2; + expect(runner.external).toEqual({}); const handleData = () => new Promise((resolve, reject) => { @@ -251,10 +253,7 @@ describe("ローカルコンテンツの動作テスト", () => { it("ローカルの game.json から V2 コンテンツを起動できる", async () => { const playManager = new PlayManager(); const playId = await playManager.createPlay({ - contentDir: path.resolve(__dirname, "fixtures", "content-v2"), - contentConfig: { - externals: ["hoge"] - } + contentDir: path.resolve(__dirname, "fixtures", "content-v2") }); const activeAMFlow = playManager.createAMFlow(playId); const playToken = playManager.createPlayToken(playId, activePermission); @@ -266,6 +265,7 @@ describe("ローカルコンテンツの動作テスト", () => { executionMode: "active" }); const runner = runnerManager.getRunner(runnerId) as RunnerV2; + expect(runner.external).toEqual({ext: "0"}); const handleData = () => new Promise((resolve, reject) => { diff --git a/packages/headless-driver/src/play/Play.ts b/packages/headless-driver/src/play/Play.ts index a1e6ba739..24c12256d 100644 --- a/packages/headless-driver/src/play/Play.ts +++ b/packages/headless-driver/src/play/Play.ts @@ -18,7 +18,4 @@ export interface PlayWithContentDir { * game.json を含むディレクトリ。 */ contentDir: string; - contentConfig?: { - externals?: string[]; - }; } diff --git a/packages/headless-driver/src/runner/RunnerManager.ts b/packages/headless-driver/src/runner/RunnerManager.ts index 4020b7fb1..f1d79d31a 100644 --- a/packages/headless-driver/src/runner/RunnerManager.ts +++ b/packages/headless-driver/src/runner/RunnerManager.ts @@ -27,6 +27,7 @@ interface GameConfiguration { definitions?: string[]; environment?: { "sandbox-runtime"?: "1" | "2"; + external: {[key: string]: string}; }; } @@ -58,20 +59,30 @@ export class RunnerManager { let engineConfiguration: EngineConfiguration; let gameConfiguration: GameConfiguration; let contentUrl: string; + let external: {[name: string]: string} = {}; + if ("contentUrl" in play) { contentUrl = play.contentUrl; engineConfiguration = await this.resolveContent(contentUrl); gameConfiguration = await this.resolveGameConfiguration(engineConfiguration.content_url); + for (let i = 0; i < engineConfiguration.external.length; i++) { + const name = engineConfiguration.external[i]; + external[name] = "0"; // NOTE: "0" 扱いとする + } } else { contentUrl = path.resolve(play.contentDir, "game.json"); - const config = play.contentConfig; + gameConfiguration = await this.resolveGameConfiguration(contentUrl); + let ext: string[] = []; + if (gameConfiguration.environment != null && gameConfiguration.environment.external != null) { + external = gameConfiguration.environment.external; + ext = Object.keys(gameConfiguration.environment.external); + } engineConfiguration = { - external: config != null && config.externals != null ? config.externals : [], + external: ext, content_url: contentUrl, asset_base_url: play.contentDir, engine_urls: [] }; - gameConfiguration = await this.resolveGameConfiguration(contentUrl); } const amflow = params.amflow; @@ -107,6 +118,7 @@ export class RunnerManager { playToken: params.playToken, amflow, executionMode: params.executionMode, + external, gameArgs: params.gameArgs, player: params.player }); @@ -126,6 +138,7 @@ export class RunnerManager { playToken: params.playToken, amflow, executionMode: params.executionMode, + external, gameArgs: params.gameArgs, player: params.player }); From ba10b83f41f68b40d5b5c248ae2570520299d074 Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Mon, 13 May 2019 16:48:01 +0900 Subject: [PATCH 06/10] Do not build before start test --- packages/headless-driver-runner-v1/package.json | 2 +- packages/headless-driver-runner-v2/package.json | 2 +- packages/headless-driver-runner/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/headless-driver-runner-v1/package.json b/packages/headless-driver-runner-v1/package.json index d9089b4e4..2b64871ee 100644 --- a/packages/headless-driver-runner-v1/package.json +++ b/packages/headless-driver-runner-v1/package.json @@ -7,7 +7,7 @@ "prepublish": "npm run build", "build": "rimraf ./lib && tsc -p ./tsconfig.json && npm run formatter", "formatter": "prettier --write \"src/**/*.ts\" --config ../prettier.config.js", - "test": "npm run build && npm run lint", + "test": "npm run lint", "lint": "tslint -c ../tslint.json src/**/*.ts -p tsconfig.json -e \"**/node_modules/**\"" }, "publishConfig": { diff --git a/packages/headless-driver-runner-v2/package.json b/packages/headless-driver-runner-v2/package.json index 791e1e066..f888ffcd5 100644 --- a/packages/headless-driver-runner-v2/package.json +++ b/packages/headless-driver-runner-v2/package.json @@ -7,7 +7,7 @@ "prepublish": "npm run build", "build": "rimraf ./lib && tsc -p ./tsconfig.json && npm run formatter", "formatter": "prettier --write \"src/**/*.ts\" --config ../prettier.config.js", - "test": "npm run build && npm run lint", + "test": "npm run lint", "lint": "tslint -c ../tslint.json src/**/*.ts -p tsconfig.json -e \"**/node_modules/**\"" }, "publishConfig": { diff --git a/packages/headless-driver-runner/package.json b/packages/headless-driver-runner/package.json index 268c8dd7d..60a1ad263 100644 --- a/packages/headless-driver-runner/package.json +++ b/packages/headless-driver-runner/package.json @@ -7,7 +7,7 @@ "prepublish": "npm run build", "build": "rimraf ./lib && tsc -p ./tsconfig.json && npm run formatter", "formatter": "prettier --write \"src/**/*.ts\" --config ../prettier.config.js", - "test": "npm run build && npm run lint", + "test": "npm run lint", "lint": "tslint -c ../tslint.json src/**/*.ts -p tsconfig.json -e \"**/node_modules/**\"" }, "publishConfig": { From 4ff5c21f280b972e2f46382ed2bdaee2ea16c26e Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Tue, 14 May 2019 17:42:19 +0900 Subject: [PATCH 07/10] contentDir -> gameJsonPath --- packages/headless-driver/src/__tests__/runSpec.ts | 4 ++-- packages/headless-driver/src/play/Play.ts | 13 +++++++++---- packages/headless-driver/src/play/PlayManager.ts | 4 ++-- .../headless-driver/src/runner/RunnerManager.ts | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/headless-driver/src/__tests__/runSpec.ts b/packages/headless-driver/src/__tests__/runSpec.ts index f573610f3..0d14f0868 100644 --- a/packages/headless-driver/src/__tests__/runSpec.ts +++ b/packages/headless-driver/src/__tests__/runSpec.ts @@ -219,7 +219,7 @@ describe("ローカルコンテンツの動作テスト", () => { it("ローカルの game.json から V1 コンテンツを起動できる", async () => { const playManager = new PlayManager(); const playId = await playManager.createPlay({ - contentDir: path.resolve(__dirname, "fixtures", "content-v1") + gameJsonPath: path.resolve(__dirname, "fixtures", "content-v1", "game.json") }); const activeAMFlow = playManager.createAMFlow(playId); const playToken = playManager.createPlayToken(playId, activePermission); @@ -253,7 +253,7 @@ describe("ローカルコンテンツの動作テスト", () => { it("ローカルの game.json から V2 コンテンツを起動できる", async () => { const playManager = new PlayManager(); const playId = await playManager.createPlay({ - contentDir: path.resolve(__dirname, "fixtures", "content-v2") + gameJsonPath: path.resolve(__dirname, "fixtures", "content-v2", "game.json") }); const activeAMFlow = playManager.createAMFlow(playId); const playToken = playManager.createPlayToken(playId, activePermission); diff --git a/packages/headless-driver/src/play/Play.ts b/packages/headless-driver/src/play/Play.ts index 24c12256d..861e35020 100644 --- a/packages/headless-driver/src/play/Play.ts +++ b/packages/headless-driver/src/play/Play.ts @@ -1,6 +1,8 @@ export type PlayStatus = "preparing" | "running" | "suspending" | "broken"; -export type Play = (PlayWithContentUrl | PlayWithContentDir) & BasePlay; +export type Play = PlayLocating & BasePlay; + +export type PlayLocating = (PlayWithContentUrl | PlayWithGameJsonPath); export interface BasePlay { playId: string; @@ -10,12 +12,15 @@ export interface BasePlay { } export interface PlayWithContentUrl { + /** + * content.json の URL。 + */ contentUrl: string; } -export interface PlayWithContentDir { +export interface PlayWithGameJsonPath { /** - * game.json を含むディレクトリ。 + * game.json のディレクトリ。 */ - contentDir: string; + gameJsonPath: string; } diff --git a/packages/headless-driver/src/play/PlayManager.ts b/packages/headless-driver/src/play/PlayManager.ts index daf2e4fde..258a91462 100644 --- a/packages/headless-driver/src/play/PlayManager.ts +++ b/packages/headless-driver/src/play/PlayManager.ts @@ -1,9 +1,9 @@ import { Permission } from "@akashic/amflow"; import { AMFlowClient } from "./amflow/AMFlowClient"; import { AMFlowClientManager } from "./AMFlowClientManager"; -import { Play, PlayStatus, PlayWithContentDir, PlayWithContentUrl } from "./Play"; +import { Play, PlayLocating, PlayStatus } from "./Play"; -export type PlayManagerParameters = PlayWithContentUrl | PlayWithContentDir; +export type PlayManagerParameters = PlayLocating; export interface PlayFilter { status: PlayStatus; diff --git a/packages/headless-driver/src/runner/RunnerManager.ts b/packages/headless-driver/src/runner/RunnerManager.ts index f1d79d31a..dfdf85b34 100644 --- a/packages/headless-driver/src/runner/RunnerManager.ts +++ b/packages/headless-driver/src/runner/RunnerManager.ts @@ -70,7 +70,7 @@ export class RunnerManager { external[name] = "0"; // NOTE: "0" 扱いとする } } else { - contentUrl = path.resolve(play.contentDir, "game.json"); + contentUrl = play.gameJsonPath; gameConfiguration = await this.resolveGameConfiguration(contentUrl); let ext: string[] = []; if (gameConfiguration.environment != null && gameConfiguration.environment.external != null) { @@ -80,7 +80,7 @@ export class RunnerManager { engineConfiguration = { external: ext, content_url: contentUrl, - asset_base_url: play.contentDir, + asset_base_url: path.dirname(play.gameJsonPath), engine_urls: [] }; } From a3114f85629badd640d8c7d235cd3508fde77e43 Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Tue, 14 May 2019 17:42:45 +0900 Subject: [PATCH 08/10] Fix not to use generics --- packages/headless-driver/src/runner/RunnerManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/headless-driver/src/runner/RunnerManager.ts b/packages/headless-driver/src/runner/RunnerManager.ts index dfdf85b34..58620059d 100644 --- a/packages/headless-driver/src/runner/RunnerManager.ts +++ b/packages/headless-driver/src/runner/RunnerManager.ts @@ -202,11 +202,11 @@ export class RunnerManager { } protected async resolveContent(contentUrl: string): Promise { - return await this.loadJSON(contentUrl); + return await this.loadJSON(contentUrl); } - protected async resolveGameConfiguration(gameJsonUrl: string): Promise { - return await this.loadJSON(gameJsonUrl); + protected async resolveGameConfiguration(gameJsonUrl: string): Promise { + return await this.loadJSON(gameJsonUrl); } protected async loadJSON(contentUrl: string): Promise { From ddc8a757c70fe114ac4cf2673e4a9e1fb09d8b23 Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Tue, 14 May 2019 17:57:29 +0900 Subject: [PATCH 09/10] PlayLocating -> PlayLocation --- packages/headless-driver/src/play/Play.ts | 4 ++-- packages/headless-driver/src/play/PlayManager.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/headless-driver/src/play/Play.ts b/packages/headless-driver/src/play/Play.ts index 861e35020..a3f247778 100644 --- a/packages/headless-driver/src/play/Play.ts +++ b/packages/headless-driver/src/play/Play.ts @@ -1,8 +1,8 @@ export type PlayStatus = "preparing" | "running" | "suspending" | "broken"; -export type Play = PlayLocating & BasePlay; +export type Play = PlayLocation & BasePlay; -export type PlayLocating = (PlayWithContentUrl | PlayWithGameJsonPath); +export type PlayLocation = (PlayWithContentUrl | PlayWithGameJsonPath); export interface BasePlay { playId: string; diff --git a/packages/headless-driver/src/play/PlayManager.ts b/packages/headless-driver/src/play/PlayManager.ts index 258a91462..28428ae0e 100644 --- a/packages/headless-driver/src/play/PlayManager.ts +++ b/packages/headless-driver/src/play/PlayManager.ts @@ -1,9 +1,9 @@ import { Permission } from "@akashic/amflow"; import { AMFlowClient } from "./amflow/AMFlowClient"; import { AMFlowClientManager } from "./AMFlowClientManager"; -import { Play, PlayLocating, PlayStatus } from "./Play"; +import { Play, PlayLocation, PlayStatus } from "./Play"; -export type PlayManagerParameters = PlayLocating; +export type PlayManagerParameters = PlayLocation; export interface PlayFilter { status: PlayStatus; From a807d86715db631c0dfe905d8baefff6e12dfb1d Mon Sep 17 00:00:00 2001 From: yusuke-ogiwara Date: Wed, 15 May 2019 13:01:34 +0900 Subject: [PATCH 10/10] PlayLocation -> ContentLocation --- packages/headless-driver/src/play/Content.ts | 15 ++++++++++++++ packages/headless-driver/src/play/Play.ts | 20 +++---------------- .../headless-driver/src/play/PlayManager.ts | 5 +++-- 3 files changed, 21 insertions(+), 19 deletions(-) create mode 100644 packages/headless-driver/src/play/Content.ts diff --git a/packages/headless-driver/src/play/Content.ts b/packages/headless-driver/src/play/Content.ts new file mode 100644 index 000000000..9f53086e7 --- /dev/null +++ b/packages/headless-driver/src/play/Content.ts @@ -0,0 +1,15 @@ +export type ContentLocation = (ContentParameters | GameJsonParameters); + +interface ContentParameters { + /** + * content.json の URL。 + */ + contentUrl: string; +} + +interface GameJsonParameters { + /** + * game.json のディレクトリ。 + */ + gameJsonPath: string; +} diff --git a/packages/headless-driver/src/play/Play.ts b/packages/headless-driver/src/play/Play.ts index a3f247778..085f95c91 100644 --- a/packages/headless-driver/src/play/Play.ts +++ b/packages/headless-driver/src/play/Play.ts @@ -1,8 +1,8 @@ -export type PlayStatus = "preparing" | "running" | "suspending" | "broken"; +import { ContentLocation } from "./Content"; -export type Play = PlayLocation & BasePlay; +export type PlayStatus = "preparing" | "running" | "suspending" | "broken"; -export type PlayLocation = (PlayWithContentUrl | PlayWithGameJsonPath); +export type Play = ContentLocation & BasePlay; export interface BasePlay { playId: string; @@ -10,17 +10,3 @@ export interface BasePlay { createdAt: number; lastSuspendedAt: number | null; } - -export interface PlayWithContentUrl { - /** - * content.json の URL。 - */ - contentUrl: string; -} - -export interface PlayWithGameJsonPath { - /** - * game.json のディレクトリ。 - */ - gameJsonPath: string; -} diff --git a/packages/headless-driver/src/play/PlayManager.ts b/packages/headless-driver/src/play/PlayManager.ts index 28428ae0e..dffc2ce11 100644 --- a/packages/headless-driver/src/play/PlayManager.ts +++ b/packages/headless-driver/src/play/PlayManager.ts @@ -1,9 +1,10 @@ import { Permission } from "@akashic/amflow"; import { AMFlowClient } from "./amflow/AMFlowClient"; import { AMFlowClientManager } from "./AMFlowClientManager"; -import { Play, PlayLocation, PlayStatus } from "./Play"; +import { ContentLocation } from "./Content"; +import { Play, PlayStatus } from "./Play"; -export type PlayManagerParameters = PlayLocation; +export type PlayManagerParameters = ContentLocation; export interface PlayFilter { status: PlayStatus;