Repository: boardgameio/boardgame.io (ts)

Issue:
Cannot read property 'prngstate' of undefined
After upgrading to [v0.42.0](https://github.com/boardgameio/boardgame.io/tree/v0.42.0) the `random.D6` function is returning the following error
```
:3000/static/js/0.chunk.js:45834 Uncaught TypeError: Cannot read property 'prngstate' of undefined
    at Random._random (:3000/static/js/0.chunk.js:18067)
    at :3000/static/js/0.chunk.js:18097
    at Array.map (<anonymous>)
    at Object.predefined.<computed> [as D6] (:3000/static/js/0.chunk.js:18097)
    at Dice.roll (:3000/static/js/main.chunk.js:17418)
    at attackDiceMatch (:3000/static/js/main.chunk.js:16633)
    at :3000/static/js/0.chunk.js:17921
    at e.i.produce (:3000/static/js/0.chunk.js:20002)
    at :3000/static/js/0.chunk.js:17920
    at Object.processMove (:3000/static/js/0.chunk.js:16614)
    at :3000/static/js/0.chunk.js:16817
    at dispatch (:3000/static/js/0.chunk.js:155791)
    at :3000/static/js/0.chunk.js:13689
    at :3000/static/js/0.chunk.js:13745
    at Object.dispatch (:3000/static/js/0.chunk.js:13759)
    at Object.dispatchers.<computed> [as attackDiceMatch] (:3000/static/js/0.chunk.js:13612)
    at attackerDiceRoll (:3000/static/js/main.chunk.js:9661)
    at HTMLUnknownElement.callCallback (:3000/static/js/0.chunk.js:45688)
    at Object.invokeGuardedCallbackDev (:3000/static/js/0.chunk.js:45737)
    at invokeGuardedCallback (:3000/static/js/0.chunk.js:45797)
    at invokeGuardedCallbackAndCatchFirstError (:3000/static/js/0.chunk.js:45812)
    at executeDispatch (:3000/static/js/0.chunk.js:50047)
    at processDispatchQueueItemsInOrder (:3000/static/js/0.chunk.js:50079)
    at processDispatchQueue (:3000/static/js/0.chunk.js:50092)
    at dispatchEventsForPlugins (:3000/static/js/0.chunk.js:50103)
    at :3000/static/js/0.chunk.js:50314
    at batchedEventUpdates$1 (:3000/static/js/0.chunk.js:63999)
    at batchedEventUpdates (:3000/static/js/0.chunk.js:45486)
    at dispatchEventForPluginEventSystem (:3000/static/js/0.chunk.js:50313)
    at attemptToDispatchEvent (:3000/static/js/0.chunk.js:47796)
    at dispatchEvent (:3000/static/js/0.chunk.js:47714)
    at unstable_runWithPriority (:3000/static/js/0.chunk.js:157401)
    at runWithPriority$1 (:3000/static/js/0.chunk.js:53094)
    at discreteUpdates$1 (:3000/static/js/0.chunk.js:64016)
    at discreteUpdates (:3000/static/js/0.chunk.js:45498)
    at dispatchDiscreteEvent (:3000/static/js/0.chunk.js:47680)
```

I'm calling the function within a class.
```ts
import { Ctx } from "boardgame.io";
import { GStatus } from "../types/game";

export class Dice {
  G: GStatus;
  ctx: Ctx;

  constructor(GameStatus: GStatus, context: Ctx) {
    this.G = GameStatus;
    this.ctx = context;
  }

  roll(numberOfDice: number) {
    return this.ctx!.random!.D6(numberOfDice).sort().reverse();
  }
}
```

Apparently, the unexpected behaviour is related to the changes in this PR: https://github.com/boardgameio/boardgame.io/pull/841

Pull request:
fix(random): handle redacted state on multiplayer clients
Fixes #870

Prior to the current plugin system, the randomness API set a default seed '0' if it didn’t receive one from game state (i.e. when it was run on a multiplayer client and the randomness state was redacted). When the new plugin system was built, this fallback value was removed, but that didn’t cause errors because the PRNG seed and state were no longer redacted. After #857 (released in v0.42.2) reintroduced redacted state for plugins, the plugin broke for multiplayer clients that no longer had access to plugin state.

This commit reinstates the default seed (the result of which is discarded in any case) and adds a test for running the plugin with redacted state to catch a similar error in any future refactoring.

See 4b1c1357403ff3449e37cdfa158587f911ae3d7b for details of the randomness API before the current implementation.


Patch:
diff --git a/src/plugins/random/random.ts b/src/plugins/random/random.ts
index 4a0fe90..8654d0f 100644
--- a/src/plugins/random/random.ts
+++ b/src/plugins/random/random.ts
@@ -60,12 +60,12 @@ export class Random {
    * constructor
    * @param {object} ctx - The ctx object to initialize from.
    */
-  constructor(state: RandomState) {
+  constructor(state?: RandomState) {
     // If we are on the client, the seed is not present.
     // Just use a temporary seed to execute the move without
     // crashing it. The move state itself is discarded,
     // so the actual value doesn't matter.
-    this.state = state;
+    this.state = state || { seed: '0' };
     this.used = false;
   }
 


Test patch:
diff --git a/src/plugins/random/random.test.ts b/src/plugins/random/random.test.ts
index 7654082..724f125 100644
--- a/src/plugins/random/random.test.ts
+++ b/src/plugins/random/random.test.ts
@@ -11,6 +11,7 @@ import { makeMove } from '../../core/action-creators';
 import { CreateGameReducer } from '../../core/reducer';
 import { InitializeGame } from '../../core/initialize';
 import { Client } from '../../client/client';
+import { PlayerView } from '../main';
 
 function Init(seed) {
   return new Random({ seed });
@@ -123,6 +124,24 @@ test('Random API is not executed optimisitically', () => {
   }
 });
 
+test('Random API works when its state is redacted by playerView', () => {
+  const game = {
+    seed: 0,
+    moves: {
+      rollDie: (G, ctx) => ({ ...G, die: ctx.random.D6() }),
+    },
+  };
+
+  const opts = { game, isClient: true };
+  const reducer = CreateGameReducer(opts);
+  let state = InitializeGame({ game });
+  state.plugins = PlayerView(state, { ...opts, playerID: '0' });
+  expect(state.plugins.random.data).not.toBeDefined();
+  expect(state.G.die).not.toBeDefined();
+  state = reducer(state, makeMove('rollDie'));
+  expect(state.G.die).not.toBeDefined();
+});
+
 test('turn.onBegin has ctx APIs at the beginning of the game', () => {
   let random = null;
   let events = null;