@scrabble-solver/scrabble-solver 2.8.11 → 2.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/.next/BUILD_ID +1 -1
- package/.next/InjectManifest.js.nft.json +1 -0
- package/.next/build-manifest.json +6 -6
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/InjectManifest.js.nft.json +1 -0
- package/.next/server/chunks/413.js +36 -13
- package/.next/server/chunks/452.js +894 -0
- package/.next/server/chunks/515.js +4 -1
- package/.next/server/chunks/911.js +0 -887
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +3 -3
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +200 -0
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -0
- package/.next/server/pages/api/solve.js +7 -1
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +1 -1
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/index.html +7 -7
- package/.next/server/pages/index.js +74 -29
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +1 -0
- package/.next/static/{VJkrGviICslA_8zNVJ-g- → BiJ0RS1mh1CdONVQg_p20}/_buildManifest.js +1 -1
- package/.next/static/{VJkrGviICslA_8zNVJ-g- → BiJ0RS1mh1CdONVQg_p20}/_ssgManifest.js +0 -0
- package/.next/static/chunks/317-c5d262202c17d519.js +1 -0
- package/.next/static/chunks/pages/_app-1878e12521f2d115.js +1 -0
- package/.next/static/chunks/pages/index-14d33636a0746c22.js +1 -0
- package/.next/trace +52 -42
- package/next.config.js +11 -0
- package/package.json +17 -11
- package/src/components/SvgFontCss/SvgFontCss.tsx +2 -1
- package/src/pages/api/dictionary/[locale]/index.ts +53 -0
- package/src/pages/api/solve.ts +6 -0
- package/src/pages/index.tsx +8 -1
- package/src/sdk/fetch.ts +30 -0
- package/src/sdk/fetchJson.ts +10 -31
- package/src/sdk/getDictionary.ts +11 -0
- package/src/service-worker/dictionaries/constants.ts +3 -0
- package/src/service-worker/dictionaries/expirationManager.ts +9 -0
- package/src/service-worker/dictionaries/getDictionary.ts +22 -0
- package/src/service-worker/dictionaries/getDictionaryUrl.ts +7 -0
- package/src/service-worker/dictionaries/index.ts +2 -0
- package/src/service-worker/dictionaries/revalidateDictionary.ts +35 -0
- package/src/service-worker/getTrie.ts +26 -0
- package/src/service-worker/index.ts +22 -0
- package/src/service-worker/routeSolveRequests.ts +37 -0
- package/src/service-worker/routeVerifyRequests.ts +35 -0
- package/src/serviceWorkerManager.ts +26 -0
- package/src/state/sagas.ts +1 -0
- package/src/state/slices/settingsInitialState.ts +20 -1
- package/tsconfig.json +1 -0
- package/.next/static/chunks/317-8e8909dd2f587b64.js +0 -1
- package/.next/static/chunks/pages/_app-57c77cad0f197d93.js +0 -1
- package/.next/static/chunks/pages/index-d3360e075ca3c222.js +0 -1
package/next.config.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const WorkboxPlugin = require('workbox-webpack-plugin');
|
|
5
6
|
|
|
6
7
|
const tsConfig = fs.readFileSync(path.resolve(__dirname, 'tsconfig.json'), 'utf-8');
|
|
7
8
|
const tsConfigJson = JSON.parse(tsConfig);
|
|
@@ -39,5 +40,15 @@ module.exports = {
|
|
|
39
40
|
},
|
|
40
41
|
],
|
|
41
42
|
},
|
|
43
|
+
plugins: [
|
|
44
|
+
...config.plugins,
|
|
45
|
+
process.env.NODE_ENV === 'production'
|
|
46
|
+
? new WorkboxPlugin.InjectManifest({
|
|
47
|
+
swSrc: path.join(__dirname, 'src/service-worker/index.ts'),
|
|
48
|
+
swDest: path.join(__dirname, 'public/service-worker.js'),
|
|
49
|
+
exclude: [/\.map$/, /\.next/, /_next/, /manifest/, /\.htaccess$/, /.*\/static\/.*/, /service-worker\.js$/],
|
|
50
|
+
})
|
|
51
|
+
: undefined,
|
|
52
|
+
].filter(Boolean),
|
|
42
53
|
}),
|
|
43
54
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Scrabble Solver 2 - App",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=16"
|
|
@@ -28,15 +28,16 @@
|
|
|
28
28
|
"start": "env-cmd next start -p 3333"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@kamilmielnik/trie": "^2.0.1",
|
|
31
32
|
"@popperjs/core": "^2.11.6",
|
|
32
33
|
"@reduxjs/toolkit": "^1.8.6",
|
|
33
|
-
"@scrabble-solver/configs": "^2.
|
|
34
|
-
"@scrabble-solver/constants": "^2.
|
|
35
|
-
"@scrabble-solver/dictionaries": "^2.
|
|
36
|
-
"@scrabble-solver/logger": "^2.
|
|
37
|
-
"@scrabble-solver/solver": "^2.
|
|
38
|
-
"@scrabble-solver/types": "^2.
|
|
39
|
-
"@scrabble-solver/word-definitions": "^2.
|
|
34
|
+
"@scrabble-solver/configs": "^2.9.0",
|
|
35
|
+
"@scrabble-solver/constants": "^2.9.0",
|
|
36
|
+
"@scrabble-solver/dictionaries": "^2.9.0",
|
|
37
|
+
"@scrabble-solver/logger": "^2.9.0",
|
|
38
|
+
"@scrabble-solver/solver": "^2.9.0",
|
|
39
|
+
"@scrabble-solver/types": "^2.9.0",
|
|
40
|
+
"@scrabble-solver/word-definitions": "^2.9.0",
|
|
40
41
|
"classnames": "^2.3.2",
|
|
41
42
|
"next": "^12.3.1",
|
|
42
43
|
"normalize.css": "^8.0.1",
|
|
@@ -51,7 +52,11 @@
|
|
|
51
52
|
"redux": "^4.2.0",
|
|
52
53
|
"redux-saga": "^1.2.1",
|
|
53
54
|
"store2": "^2.14.2",
|
|
54
|
-
"uuid": "^9.0.0"
|
|
55
|
+
"uuid": "^9.0.0",
|
|
56
|
+
"workbox-expiration": "^6.5.4",
|
|
57
|
+
"workbox-precaching": "^6.5.4",
|
|
58
|
+
"workbox-routing": "^6.5.4",
|
|
59
|
+
"workbox-window": "^6.5.4"
|
|
55
60
|
},
|
|
56
61
|
"devDependencies": {
|
|
57
62
|
"@svgr/webpack": "^6.5.0",
|
|
@@ -66,7 +71,8 @@
|
|
|
66
71
|
"@types/redux-saga": "^0.10.5",
|
|
67
72
|
"@types/uuid": "^8.3.4",
|
|
68
73
|
"env-cmd": "^10.1.0",
|
|
69
|
-
"sass": "^1.55.0"
|
|
74
|
+
"sass": "^1.55.0",
|
|
75
|
+
"workbox-webpack-plugin": "^6.5.4"
|
|
70
76
|
},
|
|
71
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "f100d9d52460a68f1fb5a1059cd335029d5e46a8"
|
|
72
78
|
}
|
|
@@ -7,6 +7,7 @@ text {
|
|
|
7
7
|
font-family: 'Open Sans';
|
|
8
8
|
}`;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// eslint-disable-next-line react/no-danger
|
|
11
|
+
const SvgFontCss: FunctionComponent = () => <style type="text/css" dangerouslySetInnerHTML={{ __html: CSS }} />;
|
|
11
12
|
|
|
12
13
|
export default SvgFontCss;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
2
|
+
import logger from '@scrabble-solver/logger';
|
|
3
|
+
import { isLocale, Locale } from '@scrabble-solver/types';
|
|
4
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
5
|
+
|
|
6
|
+
import { getServerLoggingData } from 'api';
|
|
7
|
+
|
|
8
|
+
interface RequestData {
|
|
9
|
+
locale: Locale;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const dictionary = async (request: NextApiRequest, response: NextApiResponse): Promise<void> => {
|
|
13
|
+
const meta = getServerLoggingData(request);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const { locale } = parseRequest(request);
|
|
17
|
+
|
|
18
|
+
logger.info('dictionary - request', {
|
|
19
|
+
meta,
|
|
20
|
+
payload: {
|
|
21
|
+
locale,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const trie = await dictionaries.get(locale);
|
|
26
|
+
response.status(200).send(trie.serialize());
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
29
|
+
logger.error('dictionary - error', { error, meta });
|
|
30
|
+
response.status(500).send({ error: 'Server error', message });
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const parseRequest = (request: NextApiRequest): RequestData => {
|
|
36
|
+
const { locale } = request.query;
|
|
37
|
+
|
|
38
|
+
if (!isLocale(locale)) {
|
|
39
|
+
throw new Error('Invalid "locale" parameter');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
locale,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const config = {
|
|
48
|
+
api: {
|
|
49
|
+
responseLimit: '25mb',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default dictionary;
|
package/src/pages/api/solve.ts
CHANGED
package/src/pages/index.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { AnimationEvent, FormEvent, FunctionComponent, useState } from 'react';
|
|
4
|
+
import { AnimationEvent, FormEvent, FunctionComponent, useEffect, useState } from 'react';
|
|
5
5
|
import Modal from 'react-modal';
|
|
6
6
|
import { useDispatch } from 'react-redux';
|
|
7
7
|
import { useEffectOnce, useMeasure } from 'react-use';
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { useIsTablet, useLocalStorage } from 'hooks';
|
|
25
25
|
import { getCellSize } from 'lib';
|
|
26
26
|
import { COMPONENTS_SPACING, COMPONENTS_SPACING_MOBILE, DICTIONARY_HEIGHT } from 'parameters';
|
|
27
|
+
import { registerServiceWorker } from 'serviceWorkerManager';
|
|
27
28
|
import { initialize, localStorage, reset, selectConfig, solveSlice, useTypedSelector } from 'state';
|
|
28
29
|
|
|
29
30
|
import styles from './index.module.scss';
|
|
@@ -72,6 +73,12 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
72
73
|
dispatch(initialize());
|
|
73
74
|
});
|
|
74
75
|
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (process.env.NODE_ENV === 'production') {
|
|
78
|
+
registerServiceWorker();
|
|
79
|
+
}
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
75
82
|
return (
|
|
76
83
|
<>
|
|
77
84
|
<div className={classNames(styles.index, { [styles.initialized]: isInitialized })}>
|
package/src/sdk/fetch.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isError } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
const fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
4
|
+
let response: Response;
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
response = await window.fetch(input, init);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
const message = isError(error) ? error.message : 'Unknown error';
|
|
10
|
+
throw new Error(`Network error: ${message}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (response.ok) {
|
|
14
|
+
return response;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const json = await response.json();
|
|
19
|
+
|
|
20
|
+
if (isError(json)) {
|
|
21
|
+
throw new Error(json.message);
|
|
22
|
+
}
|
|
23
|
+
} finally {
|
|
24
|
+
// do nothing
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default fetch;
|
package/src/sdk/fetchJson.ts
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fetch from './fetch';
|
|
2
2
|
|
|
3
3
|
const fetchJson = async <T>(input: RequestInfo | URL, init?: RequestInit): Promise<T> => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
...init,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
} catch (error) {
|
|
15
|
-
const message = isError(error) ? error.message : 'Unknown error';
|
|
16
|
-
throw new Error(`Network error: ${message}`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (response.ok) {
|
|
20
|
-
return response.json();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const json = await response.json();
|
|
25
|
-
|
|
26
|
-
if (isError(json)) {
|
|
27
|
-
throw new Error(json.message);
|
|
28
|
-
}
|
|
29
|
-
} finally {
|
|
30
|
-
// do nothing
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4
|
+
const response = await fetch(input, {
|
|
5
|
+
...init,
|
|
6
|
+
headers: {
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
...init?.headers,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return response.json();
|
|
34
13
|
};
|
|
35
14
|
|
|
36
15
|
export default fetchJson;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
2
|
+
import { Locale } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
import fetchJson from './fetchJson';
|
|
5
|
+
|
|
6
|
+
const getDictionary = async (locale: Locale): Promise<Trie> => {
|
|
7
|
+
const serialized = await fetchJson<string>(`/api/dictionary/${locale}`);
|
|
8
|
+
return Trie.deserialize(serialized);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default getDictionary;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CacheExpiration } from 'workbox-expiration';
|
|
2
|
+
|
|
3
|
+
import { DICTIONARY_CACHE, DICTIONARY_CACHE_MAX_AGE } from './constants';
|
|
4
|
+
|
|
5
|
+
const expirationManager = new CacheExpiration(DICTIONARY_CACHE, {
|
|
6
|
+
maxAgeSeconds: DICTIONARY_CACHE_MAX_AGE / 1000,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export default expirationManager;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Locale } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { DICTIONARY_CACHE } from './constants';
|
|
4
|
+
import expirationManager from './expirationManager';
|
|
5
|
+
import getDictionaryUrl from './getDictionaryUrl';
|
|
6
|
+
|
|
7
|
+
const getDictionary = async (locale: Locale): Promise<string | undefined> => {
|
|
8
|
+
await expirationManager.expireEntries();
|
|
9
|
+
|
|
10
|
+
const url = getDictionaryUrl(locale);
|
|
11
|
+
const cache = await caches.open(DICTIONARY_CACHE);
|
|
12
|
+
const cached = await cache.match(url);
|
|
13
|
+
|
|
14
|
+
if (typeof cached === 'undefined') {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const serialized = await cached.clone().text();
|
|
19
|
+
return serialized;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default getDictionary;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Locale } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { DICTIONARY_CACHE } from './constants';
|
|
4
|
+
import expirationManager from './expirationManager';
|
|
5
|
+
import getDictionaryUrl from './getDictionaryUrl';
|
|
6
|
+
|
|
7
|
+
const requests: Partial<Record<Locale, Promise<Response> | undefined>> = {};
|
|
8
|
+
|
|
9
|
+
const revalidateDictionary = async (locale: Locale): Promise<void> => {
|
|
10
|
+
if (requests[locale] instanceof Promise) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let response: Response | undefined;
|
|
15
|
+
const url = getDictionaryUrl(locale);
|
|
16
|
+
const request = fetch(url);
|
|
17
|
+
requests[locale] = request;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
response = await request;
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
requests[locale] = undefined;
|
|
24
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cache = await caches.open(DICTIONARY_CACHE);
|
|
28
|
+
await cache.put(url, response.clone());
|
|
29
|
+
await expirationManager.updateTimestamp(url);
|
|
30
|
+
} finally {
|
|
31
|
+
requests[locale] = undefined;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default revalidateDictionary;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
2
|
+
import { Locale } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
import { getDictionary } from './dictionaries';
|
|
5
|
+
|
|
6
|
+
const cache: Partial<Record<Locale, { trie: Trie; dictionary: string } | undefined>> = {};
|
|
7
|
+
|
|
8
|
+
const getTrie = async (locale: Locale): Promise<Trie | undefined> => {
|
|
9
|
+
const dictionary = await getDictionary(locale);
|
|
10
|
+
|
|
11
|
+
if (typeof dictionary === 'undefined') {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const cached = cache[locale];
|
|
16
|
+
|
|
17
|
+
if (typeof cached === 'undefined' || cached.dictionary !== dictionary) {
|
|
18
|
+
const trie = Trie.deserialize(dictionary);
|
|
19
|
+
cache[locale] = { dictionary, trie };
|
|
20
|
+
return trie;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return cached.trie;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default getTrie;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/// <reference lib="WebWorker" />
|
|
2
|
+
|
|
3
|
+
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
|
|
4
|
+
|
|
5
|
+
import routeSolveRequests from './routeSolveRequests';
|
|
6
|
+
import routeVerifyRequests from './routeVerifyRequests';
|
|
7
|
+
|
|
8
|
+
declare const self: ServiceWorkerGlobalScope;
|
|
9
|
+
|
|
10
|
+
self.addEventListener('install', () => {
|
|
11
|
+
self.skipWaiting();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
self.addEventListener('activate', () => {
|
|
15
|
+
self.clients.claim();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
cleanupOutdatedCaches();
|
|
19
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
20
|
+
precacheAndRoute(self.__WB_MANIFEST);
|
|
21
|
+
routeSolveRequests();
|
|
22
|
+
routeVerifyRequests();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getConfig } from '@scrabble-solver/configs';
|
|
2
|
+
import { BLANK } from '@scrabble-solver/constants';
|
|
3
|
+
import { solve } from '@scrabble-solver/solver';
|
|
4
|
+
import { Board, Locale, Tile } from '@scrabble-solver/types';
|
|
5
|
+
import { registerRoute } from 'workbox-routing';
|
|
6
|
+
|
|
7
|
+
import { revalidateDictionary } from './dictionaries';
|
|
8
|
+
import getTrie from './getTrie';
|
|
9
|
+
|
|
10
|
+
const headers = {
|
|
11
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const routeSolveRequests = () => {
|
|
15
|
+
registerRoute(
|
|
16
|
+
({ url }) => url.origin === location.origin && url.pathname === '/api/solve',
|
|
17
|
+
async ({ request }) => {
|
|
18
|
+
const { board, characters, configId, locale } = await request.clone().json();
|
|
19
|
+
const trie = await getTrie(locale);
|
|
20
|
+
|
|
21
|
+
if (!trie) {
|
|
22
|
+
const response = await fetch(request);
|
|
23
|
+
revalidateDictionary(locale);
|
|
24
|
+
return response;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const config = getConfig(configId)[locale as Locale];
|
|
28
|
+
const tiles = characters.map((character: string) => new Tile({ character, isBlank: character === BLANK }));
|
|
29
|
+
const resultsJson = solve(trie, config, Board.fromJson(board), tiles);
|
|
30
|
+
const json = JSON.stringify(resultsJson);
|
|
31
|
+
return new Response(json, { headers });
|
|
32
|
+
},
|
|
33
|
+
'POST',
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default routeSolveRequests;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Board } from '@scrabble-solver/types';
|
|
2
|
+
import { registerRoute } from 'workbox-routing';
|
|
3
|
+
|
|
4
|
+
import { revalidateDictionary } from './dictionaries';
|
|
5
|
+
import getTrie from './getTrie';
|
|
6
|
+
|
|
7
|
+
const headers = {
|
|
8
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const routeVerifyRequests = () => {
|
|
12
|
+
registerRoute(
|
|
13
|
+
({ url }) => url.origin === location.origin && url.pathname === '/api/verify',
|
|
14
|
+
async ({ request }) => {
|
|
15
|
+
const { board: boardJson, locale } = await request.clone().json();
|
|
16
|
+
const trie = await getTrie(locale);
|
|
17
|
+
|
|
18
|
+
if (!trie) {
|
|
19
|
+
const response = await fetch(request);
|
|
20
|
+
revalidateDictionary(locale);
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const board = Board.fromJson(boardJson);
|
|
25
|
+
const words = board.getWords().sort((a, b) => a.localeCompare(b));
|
|
26
|
+
const invalidWords = words.filter((word) => !trie.has(word));
|
|
27
|
+
const validWords = words.filter((word) => trie.has(word));
|
|
28
|
+
const json = JSON.stringify({ invalidWords, validWords });
|
|
29
|
+
return new Response(json, { headers });
|
|
30
|
+
},
|
|
31
|
+
'POST',
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default routeVerifyRequests;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Workbox } from 'workbox-window';
|
|
2
|
+
|
|
3
|
+
let serviceWorker: Workbox | null = null;
|
|
4
|
+
|
|
5
|
+
export const registerServiceWorker = () => {
|
|
6
|
+
if (!globalThis.navigator || !('serviceWorker' in globalThis.navigator)) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
serviceWorker = new Workbox('/service-worker.js');
|
|
11
|
+
serviceWorker.register({ immediate: true });
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getServiceWorker = (): Workbox | null => {
|
|
15
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (serviceWorker) {
|
|
20
|
+
return serviceWorker;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
registerServiceWorker();
|
|
24
|
+
|
|
25
|
+
return serviceWorker;
|
|
26
|
+
};
|
package/src/state/sagas.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { scrabble } from '@scrabble-solver/configs';
|
|
2
2
|
import { Locale } from '@scrabble-solver/types';
|
|
3
3
|
|
|
4
|
+
const getInitialLocale = (): Locale => {
|
|
5
|
+
if (!globalThis.navigator) {
|
|
6
|
+
return Locale.EN_US;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const locales = Object.values(Locale);
|
|
10
|
+
const exactMatch = locales.find((locale) => globalThis.navigator.language === locale);
|
|
11
|
+
|
|
12
|
+
if (exactMatch) {
|
|
13
|
+
return exactMatch;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const partialMatch = locales.find((locale) => {
|
|
17
|
+
return globalThis.navigator.language === locale.substring(0, 2);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return partialMatch || Locale.EN_US;
|
|
21
|
+
};
|
|
22
|
+
|
|
4
23
|
const settingsInitialState = {
|
|
5
24
|
autoGroupTiles: 'left' as 'left' | 'right' | null,
|
|
6
25
|
configId: scrabble.id,
|
|
7
|
-
locale:
|
|
26
|
+
locale: getInitialLocale(),
|
|
8
27
|
};
|
|
9
28
|
|
|
10
29
|
export default settingsInitialState;
|
package/tsconfig.json
CHANGED