Repository: boardgameio/boardgame.io (ts)

Issue:
playerView executed twice for multiplayer game
When using playerView to calculate results from secret info, boardgame.io crashes.

Here is a minimal reproduce.
https://codesandbox.io/s/boardgameio-typescript-player-view-issue-demo-f-hefjq?file=/src/Game.ts

Pull request description:
fix(client): Fix playerView executed twice do not strip again if is multiplayer
Fix #818 
This fix disable strip on client if current client is multiplayer .
#### Checklist

* [x] Use a separate branch in your local repo (not `master`).
* [x] Test coverage is 100% (or you have a story for why it's ok).


Patch:
diff --git a/src/client/client.ts b/src/client/client.ts
index ed69b66..187b0d5 100644
--- a/src/client/client.ts
+++ b/src/client/client.ts
@@ -435,7 +435,11 @@ export class _ClientImpl<G extends any = any> {
     // Secrets are normally stripped on the server,
     // but we also strip them here so that game developers
     // can see their effects while prototyping.
-    const G = this.game.playerView(state.G, state.ctx, this.playerID);
+    // Do not strip again if this is a multiplayer game
+    // since the server has already stripped secret info. (issue #818)
+    const G = this.multiplayer
+      ? state.G
+      : this.game.playerView(state.G, state.ctx, this.playerID);
 
     // Combine into return value.
     return {


Test patch:
diff --git a/src/client/client.test.ts b/src/client/client.test.ts
index e799a41..dd674ae 100644
--- a/src/client/client.test.ts
+++ b/src/client/client.test.ts
@@ -261,6 +261,46 @@ describe('multiplayer', () => {
   });
 });
 
+describe('strip secret only on server', () => {
+  let client0;
+  let client1;
+  let spec;
+  let initial = { secret: [1, 2, 3, 4], sum: 0 };
+  beforeAll(() => {
+    spec = {
+      game: {
+        setup: () => initial,
+        playerView: (G, ctx, playerID) => {
+          let r = { ...G };
+          r.sum = r.secret.reduce((prev, curr) => {
+            return prev + curr;
+          });
+          delete r.secret;
+          return r;
+        },
+        moves: { A: (G, ctx) => ({ A: ctx.playerID }) },
+      },
+      multiplayer: Local(),
+    };
+
+    client0 = Client({ ...spec, playerID: '0' });
+    client1 = Client({ ...spec, playerID: '1' });
+
+    client0.start();
+    client1.start();
+  });
+
+  test('secret stripped', () => {
+    expect(client0.getState().G).toEqual({ sum: 10 });
+    expect(client1.getState().G).toEqual({ sum: 10 });
+  });
+
+  afterAll(() => {
+    client0.stop();
+    client1.stop();
+  });
+});
+
 test('accepts enhancer for store', () => {
   let spyDispatcher;
   const spyEnhancer = vanillaCreateStore => (...args) => {