@rich-automation/lotto 1.0.0 → 2.0.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/README.md +85 -72
- package/lib/cjs/controllers/api/index.js +28 -0
- package/lib/cjs/controllers/factory.js +20 -6
- package/lib/cjs/controllers/playwright/index.js +1 -2
- package/lib/cjs/controllers/puppeteer/index.js +2 -6
- package/lib/cjs/lottoError.js +6 -1
- package/lib/cjs/lottoService.js +14 -3
- package/lib/esm/controllers/api/index.js +25 -0
- package/lib/esm/controllers/factory.js +20 -6
- package/lib/esm/controllers/playwright/index.js +1 -2
- package/lib/esm/controllers/puppeteer/index.js +2 -3
- package/lib/esm/controllers/puppeteer/puppeteer.page.js +0 -1
- package/lib/esm/lottoError.js +6 -1
- package/lib/esm/lottoService.js +14 -3
- package/lib/typescript/controllers/api/index.d.ts +13 -0
- package/lib/typescript/controllers/factory.d.ts +2 -2
- package/lib/typescript/controllers/playwright/index.d.ts +4 -3
- package/lib/typescript/controllers/puppeteer/index.d.ts +4 -4
- package/lib/typescript/controllers/puppeteer/puppeteer.page.d.ts +1 -1
- package/lib/typescript/lottoError.d.ts +4 -1
- package/lib/typescript/lottoService.d.ts +1 -1
- package/lib/typescript/types.d.ts +7 -4
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# @rich-automation/lotto
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@rich-automation/lotto)
|
|
4
|
-
[](https://github.com/rich-automation/lotto-module/actions/workflows/ci.yml)
|
|
3
|
+
[](https://www.npmjs.com/package/@rich-automation/lotto)
|
|
4
|
+
[](https://github.com/rich-automation/lotto-module/actions/workflows/ci.yml)
|
|
5
5
|
[](https://codecov.io/gh/rich-automation/lotto-module)
|
|
6
6
|
|
|
7
|
-
`@rich-automation/lotto
|
|
7
|
+
`@rich-automation/lotto`는 headless browser를 활용해 JS 환경에서 로또를 자동으로 구매할 수 있는 인터페이스를 제공합니다.
|
|
8
8
|
|
|
9
|
+
## 설치
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
## Installation
|
|
12
|
-
```shell
|
|
11
|
+
```bash
|
|
13
12
|
# npm
|
|
14
13
|
npm install @rich-automation/lotto
|
|
15
14
|
|
|
@@ -17,105 +16,119 @@ npm install @rich-automation/lotto
|
|
|
17
16
|
yarn add @rich-automation/lotto
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
## 준비 사항
|
|
20
|
+
|
|
21
|
+
1. 내부적으로 Playwright 또는 Puppeteer를 사용하므로, Chromium이 사전에 설치되어 있어야 합니다. ([링크](https://github.com/rich-automation/lotto-module/blob/main/package.json#L38-L39))
|
|
22
|
+
2. 구매는 [동행복권](https://dhlottery.co.kr/common.do?method=main) 사이트에서 진행되며, 예치금 충전 기능은 없으므로 미리 계정에 예치금을 충전해두어야 합니다.
|
|
23
|
+
|
|
24
|
+
## 사용법
|
|
25
|
+
|
|
26
|
+
### 기본 설정
|
|
23
27
|
|
|
24
|
-
## Usage
|
|
25
|
-
### 공통
|
|
26
|
-
lottoService 객체 인스턴스를 생성합니다.
|
|
27
28
|
```js
|
|
28
29
|
import { LottoService } from '@rich-automation/lotto';
|
|
30
|
+
import { chromium } from 'playwright';
|
|
31
|
+
import puppeteer from 'puppeteer';
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
// playwright 로 시작, 모든 기능 사용 가능
|
|
34
|
+
const lottoService = new LottoService({
|
|
35
|
+
controller: chromium // puppeteer
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// puppeteer 로 시작, 모든 기능 사용 가능
|
|
39
|
+
const lottoService = new LottoService({
|
|
40
|
+
controller: puppeteer
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// API 모드로 시작, check / getCheckWinningLink 기능만 사용 가능
|
|
44
|
+
const lottoService = new LottoService({
|
|
45
|
+
controller: 'api'
|
|
46
|
+
});
|
|
32
47
|
```
|
|
33
|
-
LottoService 클래스의 생성자는 BrowserConfigs를 인수로 받을 수 있습니다. 인수로 전달된 configs는 다음과 같은 속성을 가질 수 있습니다:
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
49
|
+
옵션 설명:
|
|
50
|
+
|
|
51
|
+
- `controller`: (필수) `puppeteer`, `playwright` 모듈 또는 `"api"`
|
|
52
|
+
- `headless`: 기본값 `false`
|
|
53
|
+
- `defaultViewport`: 기본값 `{ width: 1080, height: 1024 }`
|
|
54
|
+
- `logLevel`: 기본값 `2` (NONE = -1, ERROR = 0, WARN = 1, INFO = 2, DEBUG = 3)
|
|
55
|
+
|
|
39
56
|
```js
|
|
40
|
-
|
|
41
|
-
import {LottoService, LogLevel} from '@rich-automation/lotto'
|
|
57
|
+
import { LottoService, LogLevel } from '@rich-automation/lotto';
|
|
42
58
|
|
|
43
59
|
const lottoService = new LottoService({
|
|
60
|
+
controller: chromium,
|
|
44
61
|
headless: true,
|
|
45
|
-
defaultViewport: { width: 1280, height: 720 },
|
|
62
|
+
defaultViewport: { width: 1280, height: 720 },
|
|
46
63
|
logLevel: LogLevel.DEBUG
|
|
47
64
|
});
|
|
48
65
|
```
|
|
49
|
-
### 구매
|
|
50
|
-
```js
|
|
51
|
-
import {LottoService} from "@rich-automation/lotto";
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
const PWD = "<YOUR_PASSWORD>";
|
|
67
|
+
### 로또 구매
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
```js
|
|
70
|
+
const ID = '<YOUR_ID>';
|
|
71
|
+
const PWD = '<YOUR_PASSWORD>';
|
|
59
72
|
|
|
60
|
-
await lottoService.signIn(ID,PWD);
|
|
73
|
+
await lottoService.signIn(ID, PWD);
|
|
61
74
|
|
|
62
75
|
const numbers = await lottoService.purchase(5);
|
|
63
|
-
console.log(numbers); //[[ 1, 14, 21, 27, 30, 44 ],[ 4, 5, 27, 29, 40, 44 ],[ 9, 18, 19, 24, 38, 42 ],[ 4, 6, 13, 20, 38, 39 ],[ 8, 9, 10, 19, 32, 40 ]]
|
|
64
|
-
|
|
65
|
-
|
|
66
76
|
|
|
77
|
+
console.log(numbers); // [[ 1, 14, 21, 27, 30, 44 ],[ 4, 5, 27, 29, 40, 44 ],[ 9, 18, 19, 24, 38, 42 ],[ 4, 6, 13, 20, 38, 39 ],[ 8, 9, 10, 19, 32, 40 ]]
|
|
67
78
|
```
|
|
68
79
|
|
|
69
80
|
### 당첨 확인
|
|
70
|
-
이번 회차 당첨 확인
|
|
71
|
-
```js
|
|
72
|
-
import {getLastLottoRound,LottoService} from "@rich-automation/lotto";
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
이번 회차 확인:
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
```js
|
|
85
|
+
import { getLastLottoRound } from '@rich-automation/lotto';
|
|
86
|
+
|
|
87
|
+
const numbers = [
|
|
88
|
+
[1, 2, 3, 4, 5, 6],
|
|
89
|
+
[5, 6, 7, 8, 9, 10]
|
|
90
|
+
];
|
|
79
91
|
|
|
80
92
|
const currentRound = getLastLottoRound();
|
|
81
93
|
|
|
82
|
-
const result = await lottoService.check(numbers,currentRound)
|
|
83
|
-
console.log(result) //[{rank:1,matchedNumbers:[1,2,3,4,5,6]},{rank:5,matchedNumbers:[5,6]]
|
|
84
|
-
```
|
|
85
|
-
다음 회차 당첨 링크 생성
|
|
86
|
-
```js
|
|
87
|
-
import {getNextLottoRound,LottoService} from "@rich-automation/lotto";
|
|
94
|
+
const result = await lottoService.check(numbers, currentRound);
|
|
88
95
|
|
|
89
|
-
|
|
96
|
+
console.log(result); // [{rank:1,matchedNumbers:[1,2,3,4,5,6]},{rank:5,matchedNumbers:[5,6]]
|
|
97
|
+
```
|
|
90
98
|
|
|
99
|
+
다음 회차 링크 생성:
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
101
|
+
```js
|
|
102
|
+
import { getNextLottoRound } from '@rich-automation/lotto';
|
|
95
103
|
|
|
96
104
|
const nextRound = getNextLottoRound();
|
|
97
|
-
const link = lottoService.getCheckWinningLink(numbers,nextRound);
|
|
98
|
-
console.log(link) //"https://dhlottery.co.kr/qr.do?method=winQr&v=1071q010203040506q050607080910";
|
|
105
|
+
const link = lottoService.getCheckWinningLink(numbers, nextRound);
|
|
99
106
|
|
|
107
|
+
console.log(link); // "https://dhlottery.co.kr/qr.do?method=winQr&v=1071q010203040506q050607080910";
|
|
100
108
|
```
|
|
101
109
|
|
|
110
|
+
## API
|
|
111
|
+
|
|
112
|
+
### `signIn(id: string, password: string): Promise<string>`
|
|
113
|
+
|
|
114
|
+
동행복권 로그인, 성공 시 로그인 쿠키 반환
|
|
115
|
+
|
|
116
|
+
### `signInWithCookie(cookies: string): Promise<string>`
|
|
117
|
+
|
|
118
|
+
쿠키 기반 로그인
|
|
119
|
+
|
|
120
|
+
### `purchase(amount?: number): Promise<number[][]>`
|
|
121
|
+
|
|
122
|
+
로또 번호 자동 구매 (1~5 게임)
|
|
123
|
+
|
|
124
|
+
### `check(numbers: number[][], round?: number): Promise<{ rank:number; matchedNumbers:number[] }[]>`
|
|
125
|
+
|
|
126
|
+
(API) 당첨 결과 확인
|
|
127
|
+
|
|
128
|
+
### `getCheckWinningLink(numbers: number[][], round?: number): string`
|
|
129
|
+
|
|
130
|
+
(API) 당첨 확인 링크 생성
|
|
131
|
+
|
|
132
|
+
### `destroy(): Promise<void>`
|
|
102
133
|
|
|
103
|
-
|
|
104
|
-
### signIn
|
|
105
|
-
- `(id: string, password: string) => Promise<string>`
|
|
106
|
-
- description: id와 pwd를 입력받아 동행복권에 로그인합니다. 성공할경우 로그인 쿠키를 반환합니다.
|
|
107
|
-
### signInWithCookie
|
|
108
|
-
- `(cookies: string) => Promise<string>`
|
|
109
|
-
- description: 로그인 쿠키를 입력받아 동행복권에 로그인합니다. 성공할경우 로그인 쿠키를 반환합니다.
|
|
110
|
-
### purchase
|
|
111
|
-
- `(amount?: number) => Promise<number[][]>`
|
|
112
|
-
- description: 구매할 게임 횟수를 입력받아 로또를 구매하고, 구매한 번호를 이차원 배열 형태로 반환합니다. amount는 1~5사이 값을 가집니다.
|
|
113
|
-
### check
|
|
114
|
-
- `(numbers: number[][], round?: number) => Promise<{ rank:number; matchedNumbers:number[] }[]>`
|
|
115
|
-
- description: 회차와 해당 회차에 구매한 로또번호를 입력받아 당첨 등수(rank)와 맞춘 번호(matchedNumbers)목록을 반환합니다. 회차를 지정하지 않으면 최신 회차를 기준으로 확인합니다.
|
|
116
|
-
### getCheckWinningLink
|
|
117
|
-
- `(numbers: number[][], round?: number) => string`
|
|
118
|
-
- description: 회차와 구매한 로또 번호를 입력받아 당첨 확인 링크를 생성합니다. 로또 번호는 해당 회차에 구매한 모든 게임을 이차원 배열 형태로 입력받습니다. 회차를 지정하지 않으면 다음 회차를 기준으로 링크를 생성합니다.
|
|
119
|
-
### destroy
|
|
120
|
-
- `() => Promise<void>`
|
|
121
|
-
- description: LottoService 인스턴스에서 사용한 브라우저 컨트롤러를 종료합니다.
|
|
134
|
+
브라우저 인스턴스 종료
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.APIModeController = void 0;
|
|
13
|
+
class APIModeController {
|
|
14
|
+
constructor(configs, logger) {
|
|
15
|
+
this.focus = () => __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
throw new Error('APIModeController does not support focus method');
|
|
17
|
+
});
|
|
18
|
+
this.close = () => __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
this.logger.warn('APIModeController does not support close method');
|
|
20
|
+
});
|
|
21
|
+
this.cleanPages = () => __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
this.logger.warn('APIModeController does not support cleanPages method');
|
|
23
|
+
});
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
this.configs = configs;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.APIModeController = APIModeController;
|
|
@@ -3,12 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createBrowserController = void 0;
|
|
4
4
|
const puppeteer_1 = require("./puppeteer");
|
|
5
5
|
const playwright_1 = require("./playwright");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
case 'playwright':
|
|
11
|
-
return new playwright_1.PlaywrightController(configs, logger);
|
|
6
|
+
const api_1 = require("./api");
|
|
7
|
+
function createBrowserController(configs, logger) {
|
|
8
|
+
if (isAPIMode(configs)) {
|
|
9
|
+
return new api_1.APIModeController(configs, logger);
|
|
12
10
|
}
|
|
11
|
+
if (isPlaywright(configs)) {
|
|
12
|
+
return new playwright_1.PlaywrightController(configs, logger);
|
|
13
|
+
}
|
|
14
|
+
if (isPuppeteer(configs)) {
|
|
15
|
+
return new puppeteer_1.PuppeteerController(configs, logger);
|
|
16
|
+
}
|
|
17
|
+
throw new Error('Invalid browser controller');
|
|
13
18
|
}
|
|
14
19
|
exports.createBrowserController = createBrowserController;
|
|
20
|
+
function isAPIMode(configs) {
|
|
21
|
+
return configs.controller === 'api';
|
|
22
|
+
}
|
|
23
|
+
function isPlaywright(configs) {
|
|
24
|
+
return typeof configs.controller !== 'string' && 'connectOverCDP' in configs.controller;
|
|
25
|
+
}
|
|
26
|
+
function isPuppeteer(configs) {
|
|
27
|
+
return typeof configs.controller !== 'string' && 'executablePath' in configs.controller;
|
|
28
|
+
}
|
|
@@ -10,7 +10,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.PlaywrightController = void 0;
|
|
13
|
-
const playwright_1 = require("playwright");
|
|
14
13
|
const deferred_1 = require("../../utils/deferred");
|
|
15
14
|
const playwright_page_1 = require("./playwright.page");
|
|
16
15
|
const constants_1 = require("../../constants");
|
|
@@ -69,7 +68,7 @@ class PlaywrightController {
|
|
|
69
68
|
});
|
|
70
69
|
this.configs = configs;
|
|
71
70
|
this.logger = logger;
|
|
72
|
-
|
|
71
|
+
this.configs.controller.launch(this.configs).then((browser) => __awaiter(this, void 0, void 0, function* () {
|
|
73
72
|
this.browser = browser;
|
|
74
73
|
this.context = yield browser.newContext();
|
|
75
74
|
}));
|
|
@@ -8,12 +8,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
12
|
exports.PuppeteerController = void 0;
|
|
16
|
-
const puppeteer_1 = __importDefault(require("puppeteer"));
|
|
17
13
|
const deferred_1 = require("../../utils/deferred");
|
|
18
14
|
const puppeteer_page_1 = require("./puppeteer.page");
|
|
19
15
|
const constants_1 = require("../../constants");
|
|
@@ -71,8 +67,8 @@ class PuppeteerController {
|
|
|
71
67
|
});
|
|
72
68
|
this.configs = configs;
|
|
73
69
|
this.logger = logger;
|
|
74
|
-
|
|
75
|
-
.launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true
|
|
70
|
+
configs.controller
|
|
71
|
+
.launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true }))
|
|
76
72
|
.then((browser) => __awaiter(this, void 0, void 0, function* () {
|
|
77
73
|
this.browser = browser;
|
|
78
74
|
}));
|
package/lib/cjs/lottoError.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const invertObject_1 = require("./utils/invertObject");
|
|
4
4
|
const BaseErrorCode = {
|
|
5
5
|
NETWORK_ERROR: 100001,
|
|
6
|
+
NOT_SUPPORTED: 110000,
|
|
6
7
|
UNKNOWN_ERROR: 199999
|
|
7
8
|
};
|
|
8
9
|
const LoginErrorCode = {
|
|
@@ -25,7 +26,8 @@ const ErrorMessage = {
|
|
|
25
26
|
[ErrorCode.INVALID_ROUND]: '로또 회차가 올바르지 않습니다.',
|
|
26
27
|
[ErrorCode.INVALID_LOTTO_NUMBER]: '로또 번호가 올바르지 않습니다.',
|
|
27
28
|
[ErrorCode.NOT_AUTHENTICATED]: '인증되지 않았습니다.',
|
|
28
|
-
[ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.'
|
|
29
|
+
[ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.',
|
|
30
|
+
[ErrorCode.NOT_SUPPORTED]: '지원되지 않는 기능입니다.'
|
|
29
31
|
};
|
|
30
32
|
class LottoError extends Error {
|
|
31
33
|
static NetworkError() {
|
|
@@ -52,6 +54,9 @@ class LottoError extends Error {
|
|
|
52
54
|
static PurchaseUnavailable() {
|
|
53
55
|
return new LottoError(ErrorCode.PURCHASE_UNAVAILABLE);
|
|
54
56
|
}
|
|
57
|
+
static NotSupported(message) {
|
|
58
|
+
return new LottoError(ErrorCode.NOT_SUPPORTED, message);
|
|
59
|
+
}
|
|
55
60
|
static get code() {
|
|
56
61
|
return ErrorCode;
|
|
57
62
|
}
|
package/lib/cjs/lottoService.js
CHANGED
|
@@ -30,14 +30,19 @@ const getCheckWinningLink_1 = require("./utils/getCheckWinningLink");
|
|
|
30
30
|
const getNextLottoRound_1 = require("./utils/getNextLottoRound");
|
|
31
31
|
class LottoService {
|
|
32
32
|
constructor(configs) {
|
|
33
|
-
var _a;
|
|
34
33
|
this.context = {
|
|
35
34
|
authenticated: false
|
|
36
35
|
};
|
|
37
36
|
this.destroy = () => __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
if (this.browserController.configs.controller === 'api') {
|
|
38
|
+
throw lottoError_1.default.NotSupported('API mode does not support destroy.');
|
|
39
|
+
}
|
|
38
40
|
return (0, lazyRun_1.lazyRun)(this.browserController.close, constants_1.CONST.BROWSER_DESTROY_SAFE_TIMEOUT);
|
|
39
41
|
});
|
|
40
42
|
this.signInWithCookie = (cookies) => __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
if (this.browserController.configs.controller === 'api') {
|
|
44
|
+
throw lottoError_1.default.NotSupported('API mode does not support signInWithCookie.');
|
|
45
|
+
}
|
|
41
46
|
// 쿠키 설정 & 페이지 이동
|
|
42
47
|
const page = yield this.browserController.focus(0);
|
|
43
48
|
this.logger.debug('[signInWithCookie]', 'setCookies');
|
|
@@ -59,6 +64,9 @@ class LottoService {
|
|
|
59
64
|
return page.getCookies();
|
|
60
65
|
});
|
|
61
66
|
this.signIn = (id, password) => __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
if (this.browserController.configs.controller === 'api') {
|
|
68
|
+
throw lottoError_1.default.NotSupported('API mode does not support signIn.');
|
|
69
|
+
}
|
|
62
70
|
const p = (0, deferred_1.deferred)();
|
|
63
71
|
queueMicrotask(() => __awaiter(this, void 0, void 0, function* () {
|
|
64
72
|
// 페이지 이동
|
|
@@ -99,6 +107,9 @@ class LottoService {
|
|
|
99
107
|
return p.promise;
|
|
100
108
|
});
|
|
101
109
|
this.purchase = (amount = 5) => __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
if (this.browserController.configs.controller === 'api') {
|
|
111
|
+
throw lottoError_1.default.NotSupported('API mode does not support purchase.');
|
|
112
|
+
}
|
|
102
113
|
if (!this.context.authenticated)
|
|
103
114
|
throw lottoError_1.default.NotAuthenticated();
|
|
104
115
|
this.logger.debug('[purchase]', 'validatePurchaseAvailability');
|
|
@@ -142,8 +153,8 @@ class LottoService {
|
|
|
142
153
|
this.logger.debug('[getCheckWinningLink]', 'getCheckWinningLink');
|
|
143
154
|
return (0, getCheckWinningLink_1.getCheckWinningLink)(numbers, round);
|
|
144
155
|
};
|
|
145
|
-
this.logger = new logger_1.default(configs
|
|
146
|
-
this.browserController = (0, factory_1.createBrowserController)(
|
|
156
|
+
this.logger = new logger_1.default(configs.logLevel, '[LottoService]');
|
|
157
|
+
this.browserController = (0, factory_1.createBrowserController)(Object.assign({ defaultViewport: { width: 1080, height: 1024 } }, configs), this.logger);
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
160
|
exports.LottoService = LottoService;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import {} from '../../logger';
|
|
11
|
+
export class APIModeController {
|
|
12
|
+
constructor(configs, logger) {
|
|
13
|
+
this.focus = () => __awaiter(this, void 0, void 0, function* () {
|
|
14
|
+
throw new Error('APIModeController does not support focus method');
|
|
15
|
+
});
|
|
16
|
+
this.close = () => __awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
this.logger.warn('APIModeController does not support close method');
|
|
18
|
+
});
|
|
19
|
+
this.cleanPages = () => __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
this.logger.warn('APIModeController does not support cleanPages method');
|
|
21
|
+
});
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
this.configs = configs;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { PuppeteerController } from './puppeteer';
|
|
2
2
|
import { PlaywrightController } from './playwright';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
case 'playwright':
|
|
8
|
-
return new PlaywrightController(configs, logger);
|
|
3
|
+
import { APIModeController } from './api';
|
|
4
|
+
export function createBrowserController(configs, logger) {
|
|
5
|
+
if (isAPIMode(configs)) {
|
|
6
|
+
return new APIModeController(configs, logger);
|
|
9
7
|
}
|
|
8
|
+
if (isPlaywright(configs)) {
|
|
9
|
+
return new PlaywrightController(configs, logger);
|
|
10
|
+
}
|
|
11
|
+
if (isPuppeteer(configs)) {
|
|
12
|
+
return new PuppeteerController(configs, logger);
|
|
13
|
+
}
|
|
14
|
+
throw new Error('Invalid browser controller');
|
|
15
|
+
}
|
|
16
|
+
function isAPIMode(configs) {
|
|
17
|
+
return configs.controller === 'api';
|
|
18
|
+
}
|
|
19
|
+
function isPlaywright(configs) {
|
|
20
|
+
return typeof configs.controller !== 'string' && 'connectOverCDP' in configs.controller;
|
|
21
|
+
}
|
|
22
|
+
function isPuppeteer(configs) {
|
|
23
|
+
return typeof configs.controller !== 'string' && 'executablePath' in configs.controller;
|
|
10
24
|
}
|
|
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { chromium } from 'playwright';
|
|
11
10
|
import { deferred } from '../../utils/deferred';
|
|
12
11
|
import { PlaywrightPage } from './playwright.page';
|
|
13
12
|
import { CONST } from '../../constants';
|
|
@@ -67,7 +66,7 @@ export class PlaywrightController {
|
|
|
67
66
|
});
|
|
68
67
|
this.configs = configs;
|
|
69
68
|
this.logger = logger;
|
|
70
|
-
|
|
69
|
+
this.configs.controller.launch(this.configs).then((browser) => __awaiter(this, void 0, void 0, function* () {
|
|
71
70
|
this.browser = browser;
|
|
72
71
|
this.context = yield browser.newContext();
|
|
73
72
|
}));
|
|
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import puppeteer, { Browser } from 'puppeteer';
|
|
11
10
|
import { deferred } from '../../utils/deferred';
|
|
12
11
|
import { PuppeteerPage } from './puppeteer.page';
|
|
13
12
|
import { CONST } from '../../constants';
|
|
@@ -66,8 +65,8 @@ export class PuppeteerController {
|
|
|
66
65
|
});
|
|
67
66
|
this.configs = configs;
|
|
68
67
|
this.logger = logger;
|
|
69
|
-
|
|
70
|
-
.launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true
|
|
68
|
+
configs.controller
|
|
69
|
+
.launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true }))
|
|
71
70
|
.then((browser) => __awaiter(this, void 0, void 0, function* () {
|
|
72
71
|
this.browser = browser;
|
|
73
72
|
}));
|
|
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { Page } from 'puppeteer';
|
|
11
10
|
import { deferred } from '../../utils/deferred';
|
|
12
11
|
import { lazyRun } from '../../utils/lazyRun';
|
|
13
12
|
export class PuppeteerPage {
|
package/lib/esm/lottoError.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { invertObject } from './utils/invertObject';
|
|
2
2
|
const BaseErrorCode = {
|
|
3
3
|
NETWORK_ERROR: 100001,
|
|
4
|
+
NOT_SUPPORTED: 110000,
|
|
4
5
|
UNKNOWN_ERROR: 199999
|
|
5
6
|
};
|
|
6
7
|
const LoginErrorCode = {
|
|
@@ -23,7 +24,8 @@ const ErrorMessage = {
|
|
|
23
24
|
[ErrorCode.INVALID_ROUND]: '로또 회차가 올바르지 않습니다.',
|
|
24
25
|
[ErrorCode.INVALID_LOTTO_NUMBER]: '로또 번호가 올바르지 않습니다.',
|
|
25
26
|
[ErrorCode.NOT_AUTHENTICATED]: '인증되지 않았습니다.',
|
|
26
|
-
[ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.'
|
|
27
|
+
[ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.',
|
|
28
|
+
[ErrorCode.NOT_SUPPORTED]: '지원되지 않는 기능입니다.'
|
|
27
29
|
};
|
|
28
30
|
export default class LottoError extends Error {
|
|
29
31
|
static NetworkError() {
|
|
@@ -50,6 +52,9 @@ export default class LottoError extends Error {
|
|
|
50
52
|
static PurchaseUnavailable() {
|
|
51
53
|
return new LottoError(ErrorCode.PURCHASE_UNAVAILABLE);
|
|
52
54
|
}
|
|
55
|
+
static NotSupported(message) {
|
|
56
|
+
return new LottoError(ErrorCode.NOT_SUPPORTED, message);
|
|
57
|
+
}
|
|
53
58
|
static get code() {
|
|
54
59
|
return ErrorCode;
|
|
55
60
|
}
|
package/lib/esm/lottoService.js
CHANGED
|
@@ -24,14 +24,19 @@ import { getCheckWinningLink } from './utils/getCheckWinningLink';
|
|
|
24
24
|
import { getNextLottoRound } from './utils/getNextLottoRound';
|
|
25
25
|
export class LottoService {
|
|
26
26
|
constructor(configs) {
|
|
27
|
-
var _a;
|
|
28
27
|
this.context = {
|
|
29
28
|
authenticated: false
|
|
30
29
|
};
|
|
31
30
|
this.destroy = () => __awaiter(this, void 0, void 0, function* () {
|
|
31
|
+
if (this.browserController.configs.controller === 'api') {
|
|
32
|
+
throw LottoError.NotSupported('API mode does not support destroy.');
|
|
33
|
+
}
|
|
32
34
|
return lazyRun(this.browserController.close, CONST.BROWSER_DESTROY_SAFE_TIMEOUT);
|
|
33
35
|
});
|
|
34
36
|
this.signInWithCookie = (cookies) => __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
if (this.browserController.configs.controller === 'api') {
|
|
38
|
+
throw LottoError.NotSupported('API mode does not support signInWithCookie.');
|
|
39
|
+
}
|
|
35
40
|
// 쿠키 설정 & 페이지 이동
|
|
36
41
|
const page = yield this.browserController.focus(0);
|
|
37
42
|
this.logger.debug('[signInWithCookie]', 'setCookies');
|
|
@@ -53,6 +58,9 @@ export class LottoService {
|
|
|
53
58
|
return page.getCookies();
|
|
54
59
|
});
|
|
55
60
|
this.signIn = (id, password) => __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
if (this.browserController.configs.controller === 'api') {
|
|
62
|
+
throw LottoError.NotSupported('API mode does not support signIn.');
|
|
63
|
+
}
|
|
56
64
|
const p = deferred();
|
|
57
65
|
queueMicrotask(() => __awaiter(this, void 0, void 0, function* () {
|
|
58
66
|
// 페이지 이동
|
|
@@ -93,6 +101,9 @@ export class LottoService {
|
|
|
93
101
|
return p.promise;
|
|
94
102
|
});
|
|
95
103
|
this.purchase = (amount = 5) => __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
if (this.browserController.configs.controller === 'api') {
|
|
105
|
+
throw LottoError.NotSupported('API mode does not support purchase.');
|
|
106
|
+
}
|
|
96
107
|
if (!this.context.authenticated)
|
|
97
108
|
throw LottoError.NotAuthenticated();
|
|
98
109
|
this.logger.debug('[purchase]', 'validatePurchaseAvailability');
|
|
@@ -136,7 +147,7 @@ export class LottoService {
|
|
|
136
147
|
this.logger.debug('[getCheckWinningLink]', 'getCheckWinningLink');
|
|
137
148
|
return getCheckWinningLink(numbers, round);
|
|
138
149
|
};
|
|
139
|
-
this.logger = new Logger(configs
|
|
140
|
-
this.browserController = createBrowserController(
|
|
150
|
+
this.logger = new Logger(configs.logLevel, '[LottoService]');
|
|
151
|
+
this.browserController = createBrowserController(Object.assign({ defaultViewport: { width: 1080, height: 1024 } }, configs), this.logger);
|
|
141
152
|
}
|
|
142
153
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BrowserConfigs, BrowserControllerInterface } from '../../types';
|
|
2
|
+
import type { Browser, BrowserContext } from 'playwright';
|
|
3
|
+
import { type LoggerInterface } from '../../logger';
|
|
4
|
+
export declare class APIModeController implements BrowserControllerInterface {
|
|
5
|
+
configs: BrowserConfigs;
|
|
6
|
+
logger: LoggerInterface;
|
|
7
|
+
browser: Browser;
|
|
8
|
+
context: BrowserContext;
|
|
9
|
+
constructor(configs: BrowserConfigs, logger: LoggerInterface);
|
|
10
|
+
focus: () => Promise<never>;
|
|
11
|
+
close: () => Promise<void>;
|
|
12
|
+
cleanPages: () => Promise<void>;
|
|
13
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { BrowserConfigs, BrowserControllerInterface } from '../types';
|
|
1
|
+
import type { BrowserConfigs, BrowserController, BrowserControllerInterface } from '../types';
|
|
2
2
|
import type { LoggerInterface } from '../logger';
|
|
3
|
-
export declare function createBrowserController
|
|
3
|
+
export declare function createBrowserController<T extends BrowserController>(configs: BrowserConfigs<T>, logger: LoggerInterface): BrowserControllerInterface;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { BrowserConfigs, BrowserControllerInterface } from '../../types';
|
|
2
2
|
import type { Browser, BrowserContext } from 'playwright';
|
|
3
|
+
import type { BrowserType } from 'playwright-core';
|
|
3
4
|
import { PlaywrightPage } from './playwright.page';
|
|
4
5
|
import { type LoggerInterface } from '../../logger';
|
|
5
|
-
export declare class PlaywrightController implements BrowserControllerInterface {
|
|
6
|
-
configs: BrowserConfigs
|
|
6
|
+
export declare class PlaywrightController implements BrowserControllerInterface<BrowserType> {
|
|
7
|
+
configs: BrowserConfigs<BrowserType>;
|
|
7
8
|
logger: LoggerInterface;
|
|
8
9
|
browser: Browser;
|
|
9
10
|
context: BrowserContext;
|
|
10
|
-
constructor(configs: BrowserConfigs
|
|
11
|
+
constructor(configs: BrowserConfigs<BrowserType>, logger: LoggerInterface);
|
|
11
12
|
private getBrowserContext;
|
|
12
13
|
focus: (pageIndex?: number) => Promise<PlaywrightPage>;
|
|
13
14
|
close: () => Promise<void>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { BrowserConfigs, BrowserControllerInterface } from '../../types';
|
|
2
|
-
import { Browser } from 'puppeteer';
|
|
3
2
|
import { PuppeteerPage } from './puppeteer.page';
|
|
4
3
|
import { type LoggerInterface } from '../../logger';
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import type { PuppeteerNode, Browser } from 'puppeteer';
|
|
5
|
+
export declare class PuppeteerController implements BrowserControllerInterface<PuppeteerNode> {
|
|
6
|
+
configs: BrowserConfigs<PuppeteerNode>;
|
|
7
7
|
logger: LoggerInterface;
|
|
8
8
|
browser: Browser;
|
|
9
|
-
constructor(configs: BrowserConfigs
|
|
9
|
+
constructor(configs: BrowserConfigs<PuppeteerNode>, logger: LoggerInterface);
|
|
10
10
|
private getBrowser;
|
|
11
11
|
focus: (pageIndex?: number) => Promise<PuppeteerPage>;
|
|
12
12
|
close: () => Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BrowserPageEvents, BrowserPageInterface, FakeDOMElement, StringifiedCookies } from '../../types';
|
|
2
|
-
import { Page } from 'puppeteer';
|
|
2
|
+
import type { Page } from 'puppeteer';
|
|
3
3
|
import type { LoggerInterface } from '../../logger';
|
|
4
4
|
export declare class PuppeteerPage implements BrowserPageInterface {
|
|
5
5
|
page: Page;
|
|
@@ -6,6 +6,7 @@ declare const ErrorCode: {
|
|
|
6
6
|
CREDENTIALS_INCORRECT: 200001;
|
|
7
7
|
INVALID_COOKIE: 200002;
|
|
8
8
|
NETWORK_ERROR: 100001;
|
|
9
|
+
NOT_SUPPORTED: 110000;
|
|
9
10
|
UNKNOWN_ERROR: 199999;
|
|
10
11
|
};
|
|
11
12
|
type ErrorCodeNumber = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
@@ -18,6 +19,7 @@ export default class LottoError extends Error {
|
|
|
18
19
|
static InvalidLottoNumber(): LottoError;
|
|
19
20
|
static NotAuthenticated(): LottoError;
|
|
20
21
|
static PurchaseUnavailable(): LottoError;
|
|
22
|
+
static NotSupported(message?: string): LottoError;
|
|
21
23
|
static get code(): {
|
|
22
24
|
INVALID_ROUND: 300001;
|
|
23
25
|
INVALID_LOTTO_NUMBER: 300002;
|
|
@@ -26,10 +28,11 @@ export default class LottoError extends Error {
|
|
|
26
28
|
CREDENTIALS_INCORRECT: 200001;
|
|
27
29
|
INVALID_COOKIE: 200002;
|
|
28
30
|
NETWORK_ERROR: 100001;
|
|
31
|
+
NOT_SUPPORTED: 110000;
|
|
29
32
|
UNKNOWN_ERROR: 199999;
|
|
30
33
|
};
|
|
31
34
|
static getMessage(code: ErrorCodeNumber): string;
|
|
32
|
-
static getName(code: ErrorCodeNumber): "INVALID_ROUND" | "INVALID_LOTTO_NUMBER" | "NOT_AUTHENTICATED" | "PURCHASE_UNAVAILABLE" | "CREDENTIALS_INCORRECT" | "INVALID_COOKIE" | "NETWORK_ERROR" | "UNKNOWN_ERROR";
|
|
35
|
+
static getName(code: ErrorCodeNumber): "INVALID_ROUND" | "INVALID_LOTTO_NUMBER" | "NOT_AUTHENTICATED" | "PURCHASE_UNAVAILABLE" | "CREDENTIALS_INCORRECT" | "INVALID_COOKIE" | "NETWORK_ERROR" | "NOT_SUPPORTED" | "UNKNOWN_ERROR";
|
|
33
36
|
code: number;
|
|
34
37
|
constructor(code: ErrorCodeNumber, message?: string);
|
|
35
38
|
}
|
|
@@ -6,7 +6,7 @@ export declare class LottoService implements LottoServiceInterface {
|
|
|
6
6
|
};
|
|
7
7
|
browserController: BrowserControllerInterface;
|
|
8
8
|
logger: LoggerInterface;
|
|
9
|
-
constructor(configs
|
|
9
|
+
constructor(configs: BrowserConfigs);
|
|
10
10
|
destroy: () => Promise<void>;
|
|
11
11
|
signInWithCookie: (cookies: string) => Promise<string>;
|
|
12
12
|
signIn: (id: string, password: string) => Promise<string>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LogLevel } from './logger';
|
|
2
|
+
import type { PuppeteerNode } from 'puppeteer';
|
|
3
|
+
import type { BrowserType } from 'playwright-core';
|
|
2
4
|
export interface LottoServiceInterface {
|
|
3
5
|
destroy(): Promise<void>;
|
|
4
6
|
signIn(id: string, password: string): Promise<string>;
|
|
@@ -10,8 +12,9 @@ export interface LottoServiceInterface {
|
|
|
10
12
|
purchase(amount: number): Promise<number[][]>;
|
|
11
13
|
getCheckWinningLink(numbers: number[][], round: number): string;
|
|
12
14
|
}
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
+
export type BrowserController = PuppeteerNode | BrowserType | 'api';
|
|
16
|
+
export interface BrowserConfigs<T extends BrowserController = BrowserController> {
|
|
17
|
+
controller: T;
|
|
15
18
|
logLevel?: LogLevel;
|
|
16
19
|
headless?: boolean;
|
|
17
20
|
defaultViewport?: {
|
|
@@ -21,8 +24,8 @@ export interface BrowserConfigs {
|
|
|
21
24
|
[key: string]: unknown;
|
|
22
25
|
args?: string[];
|
|
23
26
|
}
|
|
24
|
-
export interface BrowserControllerInterface {
|
|
25
|
-
configs: BrowserConfigs
|
|
27
|
+
export interface BrowserControllerInterface<T extends BrowserController = BrowserController> {
|
|
28
|
+
configs: BrowserConfigs<T>;
|
|
26
29
|
focus(pageIndex?: number): Promise<BrowserPageInterface>;
|
|
27
30
|
cleanPages(remainingPageIndex: number[]): Promise<void>;
|
|
28
31
|
close(): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rich-automation/lotto",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Lotto module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"build:esm": "tsc --project tsconfig.esm.json && echo '{\"type\": \"module\"}' > lib/esm/package.json",
|
|
36
36
|
"build:dts": "tsc --project tsconfig.json --emitDeclarationOnly --declaration --declarationDir lib/typescript",
|
|
37
37
|
"test": "jest --forceExit --detectOpenHandles",
|
|
38
|
-
"install:puppeteer": "node ./node_modules/puppeteer/install.
|
|
38
|
+
"install:puppeteer": "node ./node_modules/puppeteer/install.mjs",
|
|
39
39
|
"install:playwright": "npx playwright install chromium --with-deps",
|
|
40
40
|
"fix": "yarn fix:eslint && yarn fix:prettier",
|
|
41
41
|
"fix:eslint": "eslint --fix src --ext js,jsx,ts,tsx ",
|
|
@@ -46,11 +46,21 @@
|
|
|
46
46
|
"lint:prettier": "prettier --check \"src/**/*.{ts,tsx,js}\""
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"axios": "^1.
|
|
50
|
-
"dayjs": "^1.11.
|
|
49
|
+
"axios": "^1.8.4",
|
|
50
|
+
"dayjs": "^1.11.13"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
51
53
|
"playwright": "^1.35.0",
|
|
52
54
|
"puppeteer": "^20.5.0"
|
|
53
55
|
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"playwright": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"puppeteer": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
54
64
|
"devDependencies": {
|
|
55
65
|
"@babel/core": "^7.21.4",
|
|
56
66
|
"@babel/preset-env": "^7.21.4",
|
|
@@ -64,7 +74,9 @@
|
|
|
64
74
|
"eslint-config-prettier": "^8.8.0",
|
|
65
75
|
"eslint-plugin-prettier": "^4.2.1",
|
|
66
76
|
"jest": "^29.5.0",
|
|
77
|
+
"playwright": "^1.51.1",
|
|
67
78
|
"prettier": "^2.8.7",
|
|
79
|
+
"puppeteer": "^24.6.1",
|
|
68
80
|
"release-it": "^15.10.3",
|
|
69
81
|
"ts-node": "^10.9.1",
|
|
70
82
|
"typescript": "^5.0.4"
|