@couch-kit/core 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @couch-kit/core
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#36](https://github.com/faluciano/react-native-couch-kit/pull/36) [`a17371d`](https://github.com/faluciano/react-native-couch-kit/commit/a17371d9523e59c1fbffe94248bb2af080de3b51) Thanks [@faluciano](https://github.com/faluciano)! - Add CJS export for core package and fix host main field
8
+ - Added `require` export condition and CJS build step to `@couch-kit/core` for CommonJS consumers
9
+ - Fixed `@couch-kit/host` main field from `lib/index.d.ts` to `./src/index.tsx`
10
+ - Removed `typescript` from core `peerDependencies`
11
+ - Added `engines` field (`node >=18`) to all packages
12
+
13
+ ## 0.8.3
14
+
15
+ ### Patch Changes
16
+
17
+ - [#33](https://github.com/faluciano/react-native-couch-kit/pull/33) [`8773b1d`](https://github.com/faluciano/react-native-couch-kit/commit/8773b1d8687247f3f77faf116c041a26c634c8d0) Thanks [@faluciano](https://github.com/faluciano)! - Access `crypto` via `globalThis.crypto` to avoid `ReferenceError` in React Native's Hermes engine where `typeof crypto` throws instead of returning `"undefined"`. Fixes `derivePlayerId` and `generateId` on Android TV.
18
+
3
19
  ## 0.8.2
4
20
 
5
21
  ### Patch Changes
package/dist/index.cjs ADDED
@@ -0,0 +1,286 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // src/index.ts
30
+ var exports_src = {};
31
+ __export(exports_src, {
32
+ toErrorMessage: () => toErrorMessage,
33
+ replayActions: () => replayActions,
34
+ isValidSecret: () => isValidSecret,
35
+ generateId: () => generateId,
36
+ derivePlayerIdLegacy: () => derivePlayerIdLegacy,
37
+ derivePlayerId: () => derivePlayerId,
38
+ createGameReducer: () => createGameReducer,
39
+ applyMiddleware: () => applyMiddleware,
40
+ actionValidator: () => actionValidator,
41
+ actionLogger: () => actionLogger,
42
+ MessageTypes: () => MessageTypes,
43
+ MAX_PENDING_PINGS: () => MAX_PENDING_PINGS,
44
+ MAX_FRAME_SIZE: () => MAX_FRAME_SIZE,
45
+ KEEPALIVE_TIMEOUT: () => KEEPALIVE_TIMEOUT,
46
+ KEEPALIVE_INTERVAL: () => KEEPALIVE_INTERVAL,
47
+ InternalActionTypes: () => InternalActionTypes,
48
+ DEFAULT_WS_PORT_OFFSET: () => DEFAULT_WS_PORT_OFFSET,
49
+ DEFAULT_WS_PATH: () => DEFAULT_WS_PATH,
50
+ DEFAULT_SYNC_INTERVAL: () => DEFAULT_SYNC_INTERVAL,
51
+ DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
52
+ DEFAULT_MAX_DELAY: () => DEFAULT_MAX_DELAY,
53
+ DEFAULT_HTTP_PORT: () => DEFAULT_HTTP_PORT,
54
+ DEFAULT_DISCONNECT_TIMEOUT: () => DEFAULT_DISCONNECT_TIMEOUT,
55
+ DEFAULT_BASE_DELAY: () => DEFAULT_BASE_DELAY
56
+ });
57
+ module.exports = __toCommonJS(exports_src);
58
+
59
+ // src/middleware.ts
60
+ function applyMiddleware(...middlewares) {
61
+ return (reducer) => {
62
+ let currentState;
63
+ const api = {
64
+ getState: () => currentState
65
+ };
66
+ const baseDispatch = (action) => {
67
+ currentState = reducer(currentState, action);
68
+ return currentState;
69
+ };
70
+ const chain = middlewares.map((mw, index) => {
71
+ const layer = mw(api);
72
+ return (next) => {
73
+ return (action) => {
74
+ let nextCalled = false;
75
+ const safeNext = (a) => {
76
+ nextCalled = true;
77
+ return next(a);
78
+ };
79
+ const safeHandler = layer(safeNext);
80
+ try {
81
+ return safeHandler(action);
82
+ } catch (error) {
83
+ console.error(`[couch-kit] Middleware ${index} threw on action "${action.type}":`, error);
84
+ return nextCalled ? api.getState() : next(action);
85
+ }
86
+ };
87
+ };
88
+ });
89
+ const dispatch = chain.reduceRight((next, layer) => layer(next), baseDispatch);
90
+ return (state, action) => {
91
+ currentState = state;
92
+ return dispatch(action);
93
+ };
94
+ };
95
+ }
96
+ function actionLogger(options) {
97
+ const collapsed = options?.collapsed ?? true;
98
+ return (api) => (next) => (action) => {
99
+ const prevState = api.getState();
100
+ const group = collapsed ? console.groupCollapsed : console.group;
101
+ group(`[couch-kit] action ${action.type}`);
102
+ console.log("prev state", prevState);
103
+ console.log("action", action);
104
+ try {
105
+ const nextState = next(action);
106
+ console.log("next state", nextState);
107
+ return nextState;
108
+ } finally {
109
+ console.groupEnd();
110
+ }
111
+ };
112
+ }
113
+ var _internalTypes;
114
+ function getInternalTypes() {
115
+ if (!_internalTypes) {
116
+ _internalTypes = new Set(Object.values(InternalActionTypes));
117
+ }
118
+ return _internalTypes;
119
+ }
120
+ function actionValidator(schema) {
121
+ return (_api) => (next) => (action) => {
122
+ if (getInternalTypes().has(action.type))
123
+ return next(action);
124
+ const validate = schema[action.type];
125
+ if (validate && !validate(action)) {
126
+ console.warn(`[couch-kit] Action "${action.type}" failed validation, skipping`);
127
+ return _api.getState();
128
+ }
129
+ return next(action);
130
+ };
131
+ }
132
+
133
+ // src/types.ts
134
+ var InternalActionTypes = {
135
+ HYDRATE: "__HYDRATE__",
136
+ PLAYER_JOINED: "__PLAYER_JOINED__",
137
+ PLAYER_LEFT: "__PLAYER_LEFT__",
138
+ PLAYER_RECONNECTED: "__PLAYER_RECONNECTED__",
139
+ PLAYER_REMOVED: "__PLAYER_REMOVED__"
140
+ };
141
+ function createGameReducer(reducer, options) {
142
+ const baseReducer = (state, action) => {
143
+ switch (action.type) {
144
+ case InternalActionTypes.HYDRATE:
145
+ return action.payload;
146
+ case InternalActionTypes.PLAYER_JOINED: {
147
+ const { id, name, avatar } = action.payload;
148
+ return {
149
+ ...state,
150
+ players: {
151
+ ...state.players,
152
+ [id]: {
153
+ id,
154
+ name,
155
+ avatar,
156
+ isHost: false,
157
+ connected: true
158
+ }
159
+ }
160
+ };
161
+ }
162
+ case InternalActionTypes.PLAYER_LEFT: {
163
+ const { playerId } = action.payload;
164
+ const player = state.players[playerId];
165
+ if (!player)
166
+ return state;
167
+ return {
168
+ ...state,
169
+ players: {
170
+ ...state.players,
171
+ [playerId]: { ...player, connected: false }
172
+ }
173
+ };
174
+ }
175
+ case InternalActionTypes.PLAYER_RECONNECTED: {
176
+ const { playerId } = action.payload;
177
+ const player = state.players[playerId];
178
+ if (!player)
179
+ return state;
180
+ return {
181
+ ...state,
182
+ players: {
183
+ ...state.players,
184
+ [playerId]: { ...player, connected: true }
185
+ }
186
+ };
187
+ }
188
+ case InternalActionTypes.PLAYER_REMOVED: {
189
+ const { playerId } = action.payload;
190
+ if (!state.players[playerId])
191
+ return state;
192
+ const { [playerId]: _, ...remainingPlayers } = state.players;
193
+ return {
194
+ ...state,
195
+ players: remainingPlayers
196
+ };
197
+ }
198
+ default:
199
+ return reducer(state, action);
200
+ }
201
+ };
202
+ const middlewares = options?.middleware;
203
+ if (!middlewares || middlewares.length === 0)
204
+ return baseReducer;
205
+ return applyMiddleware(...middlewares)(baseReducer);
206
+ }
207
+ // src/protocol.ts
208
+ var MessageTypes = {
209
+ JOIN: "JOIN",
210
+ ACTION: "ACTION",
211
+ PING: "PING",
212
+ ASSETS_LOADED: "ASSETS_LOADED",
213
+ WELCOME: "WELCOME",
214
+ STATE_UPDATE: "STATE_UPDATE",
215
+ PONG: "PONG",
216
+ RECONNECTED: "RECONNECTED",
217
+ ERROR: "ERROR"
218
+ };
219
+ // src/constants.ts
220
+ var DEFAULT_HTTP_PORT = 8080;
221
+ var DEFAULT_WS_PORT_OFFSET = 2;
222
+ var DEFAULT_WS_PATH = "/ws";
223
+ var MAX_FRAME_SIZE = 1024 * 1024;
224
+ var DEFAULT_MAX_RETRIES = 5;
225
+ var DEFAULT_BASE_DELAY = 1000;
226
+ var DEFAULT_MAX_DELAY = 1e4;
227
+ var DEFAULT_SYNC_INTERVAL = 5000;
228
+ var MAX_PENDING_PINGS = 50;
229
+ var DEFAULT_DISCONNECT_TIMEOUT = 5 * 60 * 1000;
230
+ var KEEPALIVE_INTERVAL = 30000;
231
+ var KEEPALIVE_TIMEOUT = 1e4;
232
+ function generateId() {
233
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
234
+ return globalThis.crypto.randomUUID();
235
+ }
236
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
237
+ const bytes = new Uint8Array(16);
238
+ globalThis.crypto.getRandomValues(bytes);
239
+ return Array.from(bytes).map((b2) => b2.toString(16).padStart(2, "0")).join("");
240
+ }
241
+ const a = Math.random().toString(36).substring(2, 15);
242
+ const b = Math.random().toString(36).substring(2, 10);
243
+ return a + b;
244
+ }
245
+ function isValidSecret(secret) {
246
+ const hex = secret.replace(/-/g, "");
247
+ return hex.length >= 32 && /^[0-9a-f]+$/i.test(hex);
248
+ }
249
+ async function derivePlayerId(secret) {
250
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle?.digest === "function") {
251
+ const data = new TextEncoder().encode(secret);
252
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", data);
253
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
254
+ }
255
+ return derivePlayerIdLegacy(secret);
256
+ }
257
+ function derivePlayerIdLegacy(secret) {
258
+ return secret.replace(/-/g, "").slice(0, 16);
259
+ }
260
+ function toErrorMessage(error) {
261
+ if (error instanceof Error)
262
+ return error.message;
263
+ return String(error);
264
+ }
265
+ // src/replay.ts
266
+ function replayActions(recording, reducer) {
267
+ const snapshots = [];
268
+ let currentState = recording.initialState;
269
+ for (let i = 0;i < recording.actions.length; i++) {
270
+ const { action, timestamp } = recording.actions[i];
271
+ currentState = reducer(currentState, action);
272
+ snapshots.push({
273
+ state: currentState,
274
+ action,
275
+ timestamp,
276
+ index: i
277
+ });
278
+ }
279
+ const endTimestamp = recording.endTimestamp ?? (recording.actions.length > 0 ? recording.actions[recording.actions.length - 1].timestamp : recording.startTimestamp);
280
+ return {
281
+ finalState: currentState,
282
+ snapshots,
283
+ duration: endTimestamp - recording.startTimestamp,
284
+ actionCount: recording.actions.length
285
+ };
286
+ }
package/dist/index.js CHANGED
@@ -172,12 +172,12 @@ var DEFAULT_DISCONNECT_TIMEOUT = 5 * 60 * 1000;
172
172
  var KEEPALIVE_INTERVAL = 30000;
173
173
  var KEEPALIVE_TIMEOUT = 1e4;
174
174
  function generateId() {
175
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
176
- return crypto.randomUUID();
175
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
176
+ return globalThis.crypto.randomUUID();
177
177
  }
178
- if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
178
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
179
179
  const bytes = new Uint8Array(16);
180
- crypto.getRandomValues(bytes);
180
+ globalThis.crypto.getRandomValues(bytes);
181
181
  return Array.from(bytes).map((b2) => b2.toString(16).padStart(2, "0")).join("");
182
182
  }
183
183
  const a = Math.random().toString(36).substring(2, 15);
@@ -189,9 +189,9 @@ function isValidSecret(secret) {
189
189
  return hex.length >= 32 && /^[0-9a-f]+$/i.test(hex);
190
190
  }
191
191
  async function derivePlayerId(secret) {
192
- if (typeof crypto !== "undefined" && typeof crypto.subtle?.digest === "function") {
192
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle?.digest === "function") {
193
193
  const data = new TextEncoder().encode(secret);
194
- const hash = await crypto.subtle.digest("SHA-256", data);
194
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", data);
195
195
  return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
196
196
  }
197
197
  return derivePlayerIdLegacy(secret);
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@couch-kit/core",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
7
7
  },
8
8
  "description": "Shared types, protocol, and game reducer for Couch Kit party games",
9
9
  "license": "MIT",
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
10
13
  "repository": {
11
14
  "type": "git",
12
15
  "url": "https://github.com/faluciano/react-native-couch-kit.git",
@@ -26,6 +29,7 @@
26
29
  ".": {
27
30
  "types": "./lib/index.d.ts",
28
31
  "import": "./dist/index.js",
32
+ "require": "./dist/index.cjs",
29
33
  "default": "./dist/index.js"
30
34
  }
31
35
  },
@@ -38,7 +42,7 @@
38
42
  ],
39
43
  "sideEffects": false,
40
44
  "scripts": {
41
- "build": "bun build ./src/index.ts --outdir ./dist --target browser && tsc -p tsconfig.build.json",
45
+ "build": "bun build ./src/index.ts --outdir ./dist --target browser && bun build ./src/index.ts --outfile ./dist/index.cjs --target browser --format cjs && tsc -p tsconfig.build.json",
42
46
  "prepublishOnly": "bun run build",
43
47
  "test": "bun test",
44
48
  "lint": "eslint src/",
@@ -46,10 +50,6 @@
46
50
  "clean": "rm -rf dist lib"
47
51
  },
48
52
  "devDependencies": {
49
- "typescript": "^5.4.0",
50
- "eslint": "^8.57.0"
51
- },
52
- "peerDependencies": {
53
- "typescript": "^5.0.0"
53
+ "typescript": "^5.4.0"
54
54
  }
55
55
  }
package/src/constants.ts CHANGED
@@ -44,19 +44,19 @@ export const KEEPALIVE_TIMEOUT = 10000;
44
44
  */
45
45
  export function generateId(): string {
46
46
  if (
47
- typeof crypto !== "undefined" &&
48
- typeof crypto.randomUUID === "function"
47
+ typeof globalThis.crypto !== "undefined" &&
48
+ typeof globalThis.crypto.randomUUID === "function"
49
49
  ) {
50
- return crypto.randomUUID();
50
+ return globalThis.crypto.randomUUID();
51
51
  }
52
52
 
53
53
  // Fallback: 32 hex chars from crypto.getRandomValues
54
54
  if (
55
- typeof crypto !== "undefined" &&
56
- typeof crypto.getRandomValues === "function"
55
+ typeof globalThis.crypto !== "undefined" &&
56
+ typeof globalThis.crypto.getRandomValues === "function"
57
57
  ) {
58
58
  const bytes = new Uint8Array(16);
59
- crypto.getRandomValues(bytes);
59
+ globalThis.crypto.getRandomValues(bytes);
60
60
  return Array.from(bytes)
61
61
  .map((b) => b.toString(16).padStart(2, "0"))
62
62
  .join("");
@@ -87,11 +87,11 @@ export function isValidSecret(secret: string): boolean {
87
87
  */
88
88
  export async function derivePlayerId(secret: string): Promise<string> {
89
89
  if (
90
- typeof crypto !== "undefined" &&
91
- typeof crypto.subtle?.digest === "function"
90
+ typeof globalThis.crypto !== "undefined" &&
91
+ typeof globalThis.crypto.subtle?.digest === "function"
92
92
  ) {
93
93
  const data = new TextEncoder().encode(secret);
94
- const hash = await crypto.subtle.digest("SHA-256", data);
94
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", data);
95
95
  return Array.from(new Uint8Array(hash))
96
96
  .map((b) => b.toString(16).padStart(2, "0"))
97
97
  .join("")