@crowsgear/escl-protocol-scanner 0.1.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.
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /**
3
+ * eSCL Scanner Discovery via Python Subprocess
4
+ * Uses Python's zeroconf library for mDNS discovery
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.ESCLDiscovery = void 0;
41
+ exports.discoverScanners = discoverScanners;
42
+ const child_process_1 = require("child_process");
43
+ const path = __importStar(require("path"));
44
+ const fs = __importStar(require("fs"));
45
+ /**
46
+ * eSCL Scanner Discovery Service
47
+ * Spawns Python subprocess to handle mDNS discovery using zeroconf
48
+ */
49
+ class ESCLDiscovery {
50
+ constructor(timeout, options) {
51
+ this.pythonProcess = null;
52
+ this.discovered = new Map();
53
+ this.listeners = new Set();
54
+ this.timeout = 5000;
55
+ this.processReady = false;
56
+ this.pythonPath = 'python3';
57
+ if (timeout) {
58
+ this.timeout = timeout;
59
+ }
60
+ if (options?.pythonPath) {
61
+ this.pythonPath = options.pythonPath;
62
+ }
63
+ }
64
+ /**
65
+ * Validate Python path exists and is executable
66
+ * @throws Error if Python path is invalid
67
+ */
68
+ validatePythonPath() {
69
+ // Check if it's an absolute path
70
+ if (path.isAbsolute(this.pythonPath)) {
71
+ if (!fs.existsSync(this.pythonPath)) {
72
+ throw new Error(`Python executable not found at: ${this.pythonPath}\n` +
73
+ `Please ensure the Python virtual environment exists or provide a valid python3 path.`);
74
+ }
75
+ return;
76
+ }
77
+ // For relative paths like './venv/bin/python3', check from current working directory
78
+ const resolvedPath = path.resolve(process.cwd(), this.pythonPath);
79
+ if (!fs.existsSync(resolvedPath)) {
80
+ throw new Error(`Python executable not found at: ${resolvedPath}\n` +
81
+ `Current working directory: ${process.cwd()}\n` +
82
+ `Please ensure the Python virtual environment exists or provide a valid python3 path.`);
83
+ }
84
+ }
85
+ /**
86
+ * Start discovering scanners
87
+ * @param timeout Optional timeout in milliseconds (default: 5000ms)
88
+ * @returns Promise resolving with discovery response containing success status and scanner data
89
+ */
90
+ async startDiscovery(timeout) {
91
+ const discoveryTimeout = timeout || this.timeout;
92
+ return new Promise((resolve, reject) => {
93
+ try {
94
+ // Validate Python path before proceeding
95
+ this.validatePythonPath();
96
+ this.discovered.clear();
97
+ let resolved = false;
98
+ // Timeout fallback (in case Python doesn't respond)
99
+ const timeoutHandle = setTimeout(() => {
100
+ if (!resolved) {
101
+ resolved = true;
102
+ this.stopDiscovery();
103
+ const scanners = Array.from(this.discovered.values());
104
+ resolve({
105
+ success: true,
106
+ data: scanners
107
+ });
108
+ }
109
+ }, discoveryTimeout);
110
+ // Set up listener for Python response
111
+ const responseHandler = (scanners) => {
112
+ if (!resolved) {
113
+ resolved = true;
114
+ clearTimeout(timeoutHandle);
115
+ this.offScannerDiscovered(responseHandler);
116
+ this.stopDiscovery();
117
+ resolve({
118
+ success: true,
119
+ data: scanners
120
+ });
121
+ }
122
+ };
123
+ this.onScannerDiscovered(responseHandler);
124
+ this.startPythonService();
125
+ // Send list command to Python subprocess
126
+ if (this.pythonProcess && this.pythonProcess.stdin) {
127
+ const command = { action: 'list' };
128
+ this.pythonProcess.stdin.write(JSON.stringify(command) + '\n');
129
+ }
130
+ }
131
+ catch (error) {
132
+ reject(error);
133
+ }
134
+ });
135
+ }
136
+ /**
137
+ * Stop active discovery and cleanup subprocess
138
+ */
139
+ stopDiscovery() {
140
+ this.cleanup();
141
+ }
142
+ /**
143
+ * Get currently discovered scanners
144
+ */
145
+ getScanners() {
146
+ return Array.from(this.discovered.values());
147
+ }
148
+ /**
149
+ * Subscribe to scanner discovery updates
150
+ */
151
+ onScannerDiscovered(callback) {
152
+ this.listeners.add(callback);
153
+ }
154
+ /**
155
+ * Unsubscribe from scanner discovery updates
156
+ */
157
+ offScannerDiscovered(callback) {
158
+ this.listeners.delete(callback);
159
+ }
160
+ /**
161
+ * Start Python subprocess for eSCL operations
162
+ */
163
+ startPythonService() {
164
+ if (this.pythonProcess) {
165
+ return; // Already running
166
+ }
167
+ try {
168
+ // Get path to Python script relative to this module
169
+ const pythonScriptPath = path.join(__dirname, '..', 'python', 'escl_main.py');
170
+ this.pythonProcess = (0, child_process_1.spawn)(this.pythonPath, [pythonScriptPath], {
171
+ stdio: ['pipe', 'pipe', 'pipe'],
172
+ windowsHide: true
173
+ });
174
+ // Handle stdout - parse JSON responses
175
+ if (this.pythonProcess.stdout) {
176
+ this.pythonProcess.stdout.on('data', (data) => {
177
+ try {
178
+ const lines = data.toString().split('\n');
179
+ for (const line of lines) {
180
+ if (line.trim()) {
181
+ const response = JSON.parse(line);
182
+ if (!response.success && response.error) {
183
+ // Handle error response from Python
184
+ console.error('[eSCL Error]', response.error);
185
+ }
186
+ else if (response.success && response.scanners) {
187
+ this.discovered.clear();
188
+ for (const scanner of response.scanners) {
189
+ this.discovered.set(scanner.name, scanner);
190
+ }
191
+ this.notifyListeners();
192
+ }
193
+ }
194
+ }
195
+ }
196
+ catch (error) {
197
+ console.error('Error parsing Python response:', error);
198
+ }
199
+ });
200
+ }
201
+ // Handle stderr - logging
202
+ if (this.pythonProcess.stderr) {
203
+ this.pythonProcess.stderr.on('data', (data) => {
204
+ console.error('[eSCL Python]', data.toString());
205
+ });
206
+ }
207
+ // Handle process exit
208
+ this.pythonProcess.on('exit', (code) => {
209
+ console.log(`[eSCL] Python subprocess exited with code ${code}`);
210
+ this.pythonProcess = null;
211
+ this.processReady = false;
212
+ });
213
+ this.processReady = true;
214
+ }
215
+ catch (error) {
216
+ console.error('Failed to start Python service:', error);
217
+ this.pythonProcess = null;
218
+ }
219
+ }
220
+ /**
221
+ * Cleanup Python subprocess
222
+ */
223
+ cleanup() {
224
+ if (this.pythonProcess) {
225
+ try {
226
+ // Send exit command
227
+ if (this.pythonProcess.stdin) {
228
+ const command = { action: 'exit' };
229
+ this.pythonProcess.stdin.write(JSON.stringify(command) + '\n');
230
+ }
231
+ // Force kill after timeout
232
+ setTimeout(() => {
233
+ if (this.pythonProcess) {
234
+ this.pythonProcess.kill();
235
+ this.pythonProcess = null;
236
+ }
237
+ }, 1000);
238
+ }
239
+ catch (error) {
240
+ console.error('Error during cleanup:', error);
241
+ if (this.pythonProcess) {
242
+ this.pythonProcess.kill();
243
+ this.pythonProcess = null;
244
+ }
245
+ }
246
+ }
247
+ this.processReady = false;
248
+ }
249
+ /**
250
+ * Notify all listeners of scanner changes
251
+ */
252
+ notifyListeners() {
253
+ const scanners = Array.from(this.discovered.values());
254
+ this.listeners.forEach((callback) => {
255
+ callback(scanners);
256
+ });
257
+ }
258
+ }
259
+ exports.ESCLDiscovery = ESCLDiscovery;
260
+ /**
261
+ * Convenience function for quick scanner discovery
262
+ * @param timeout Discovery timeout in milliseconds (default: 5000)
263
+ * @returns Discovery response with success status and scanner data
264
+ */
265
+ async function discoverScanners(timeout = 5000) {
266
+ const discovery = new ESCLDiscovery(timeout);
267
+ return discovery.startDiscovery(timeout);
268
+ }
269
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @escl-protocol/scanner
3
+ * eSCL/AirPrint Protocol Scanner Library
4
+ *
5
+ * A comprehensive TypeScript/Node.js library for discovering and communicating
6
+ * with network scanners using the eSCL (eSC Lexmark) protocol, which is based
7
+ * on AirPrint standards.
8
+ *
9
+ * Features:
10
+ * - Automatic scanner discovery via mDNS/Bonjour
11
+ * - HTTP-based communication with eSCL devices
12
+ * - Support for multiple color modes and resolutions
13
+ * - Image download and processing
14
+ * - Compatible with Canon, HP, Epson, and other MFP devices
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { discoverScanners, ESCLClient } from '@escl-protocol/scanner';
19
+ *
20
+ * // Discover available scanners
21
+ * const scanners = await discoverScanners(5000);
22
+ * console.log(`Found ${scanners.length} scanners`);
23
+ *
24
+ * // Get scanner capabilities
25
+ * const client = new ESCLClient();
26
+ * const caps = await client.getCapabilities(scanners[0]);
27
+ * console.log('Available resolutions:', caps?.resolutions);
28
+ *
29
+ * // Perform a scan
30
+ * const jobId = await client.createScanJob(
31
+ * scanners[0],
32
+ * 300, // 300 DPI
33
+ * 'RGB24', // Full color
34
+ * 'Platen' // Flatbed
35
+ * );
36
+ * ```
37
+ */
38
+ export type { ESCLScanner, ESCLCapabilities, ESCLScanParams, ScannedImage, ESCLResponse, ScannerResponse, ColorModeMap, SaveImageParams, SaveImageResult, ESCLCommand, PythonCommand, ScanParams, ProcessSpawnOptions, DiscoveryResponse } from './types';
39
+ export type { ESCLDiscoveryOptions } from './discovery';
40
+ export { ESCLClient } from './client';
41
+ export { ESCLDiscovery, discoverScanners } from './discovery';
42
+ export declare const VERSION = "1.0.0";
43
+ /**
44
+ * Quick scan helper - convenience function for common scan workflow
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { quickScan } from '@escl-protocol/scanner';
49
+ *
50
+ * // With custom save path
51
+ * const filePaths = await quickScan({
52
+ * scanner: scannerDevice,
53
+ * dpi: 300,
54
+ * mode: 'color',
55
+ * source: 'Platen',
56
+ * savePath: '/path/to/save/folder'
57
+ * });
58
+ *
59
+ * // Without save path - defaults to current working directory
60
+ * const filePaths = await quickScan({
61
+ * scanner: scannerDevice,
62
+ * dpi: 300,
63
+ * mode: 'color',
64
+ * source: 'Platen'
65
+ * });
66
+ * ```
67
+ */
68
+ export declare function quickScan(params: {
69
+ scanner: any;
70
+ dpi: number;
71
+ mode: 'bw' | 'gray' | 'color';
72
+ source: 'Platen' | 'Feeder';
73
+ timeout?: number;
74
+ savePath?: string;
75
+ }): Promise<string[] | null>;
76
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,EACf,eAAe,EACf,WAAW,EACX,aAAa,EACb,UAAU,EACV,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAIxD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG9D,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,OAAO,EAAE,GAAG,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IAC9B,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CA+E3B"}
package/dist/index.js ADDED
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ /**
3
+ * @escl-protocol/scanner
4
+ * eSCL/AirPrint Protocol Scanner Library
5
+ *
6
+ * A comprehensive TypeScript/Node.js library for discovering and communicating
7
+ * with network scanners using the eSCL (eSC Lexmark) protocol, which is based
8
+ * on AirPrint standards.
9
+ *
10
+ * Features:
11
+ * - Automatic scanner discovery via mDNS/Bonjour
12
+ * - HTTP-based communication with eSCL devices
13
+ * - Support for multiple color modes and resolutions
14
+ * - Image download and processing
15
+ * - Compatible with Canon, HP, Epson, and other MFP devices
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { discoverScanners, ESCLClient } from '@escl-protocol/scanner';
20
+ *
21
+ * // Discover available scanners
22
+ * const scanners = await discoverScanners(5000);
23
+ * console.log(`Found ${scanners.length} scanners`);
24
+ *
25
+ * // Get scanner capabilities
26
+ * const client = new ESCLClient();
27
+ * const caps = await client.getCapabilities(scanners[0]);
28
+ * console.log('Available resolutions:', caps?.resolutions);
29
+ *
30
+ * // Perform a scan
31
+ * const jobId = await client.createScanJob(
32
+ * scanners[0],
33
+ * 300, // 300 DPI
34
+ * 'RGB24', // Full color
35
+ * 'Platen' // Flatbed
36
+ * );
37
+ * ```
38
+ */
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.VERSION = exports.discoverScanners = exports.ESCLDiscovery = exports.ESCLClient = void 0;
41
+ exports.quickScan = quickScan;
42
+ // Class imports and exports
43
+ const client_1 = require("./client");
44
+ var client_2 = require("./client");
45
+ Object.defineProperty(exports, "ESCLClient", { enumerable: true, get: function () { return client_2.ESCLClient; } });
46
+ var discovery_1 = require("./discovery");
47
+ Object.defineProperty(exports, "ESCLDiscovery", { enumerable: true, get: function () { return discovery_1.ESCLDiscovery; } });
48
+ Object.defineProperty(exports, "discoverScanners", { enumerable: true, get: function () { return discovery_1.discoverScanners; } });
49
+ // Version
50
+ exports.VERSION = '1.0.0';
51
+ /**
52
+ * Quick scan helper - convenience function for common scan workflow
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * import { quickScan } from '@escl-protocol/scanner';
57
+ *
58
+ * // With custom save path
59
+ * const filePaths = await quickScan({
60
+ * scanner: scannerDevice,
61
+ * dpi: 300,
62
+ * mode: 'color',
63
+ * source: 'Platen',
64
+ * savePath: '/path/to/save/folder'
65
+ * });
66
+ *
67
+ * // Without save path - defaults to current working directory
68
+ * const filePaths = await quickScan({
69
+ * scanner: scannerDevice,
70
+ * dpi: 300,
71
+ * mode: 'color',
72
+ * source: 'Platen'
73
+ * });
74
+ * ```
75
+ */
76
+ async function quickScan(params) {
77
+ let { scanner, dpi, mode, source, timeout, savePath } = params;
78
+ const client = new client_1.ESCLClient(timeout);
79
+ // Default save path to current working directory if not provided
80
+ if (!savePath) {
81
+ savePath = process.cwd();
82
+ }
83
+ try {
84
+ // Map mode to eSCL color mode
85
+ const colorModeMap = {
86
+ 'bw': 'BlackAndWhite1',
87
+ 'gray': 'Grayscale8',
88
+ 'color': 'RGB24'
89
+ };
90
+ const colorMode = colorModeMap[mode];
91
+ if (!colorMode) {
92
+ throw new Error(`Invalid color mode: ${mode}`);
93
+ }
94
+ // 1. Create scan job
95
+ const jobId = await client.createScanJob(scanner, dpi, colorMode, source);
96
+ if (!jobId) {
97
+ throw new Error('Failed to create scan job');
98
+ }
99
+ const jobUrl = `http://${scanner.host}:${scanner.port}/eSCL/ScanJobs/${jobId}`;
100
+ console.log(`[eSCL] Scan job created: ${jobUrl}`);
101
+ // 2. Wait for scan to complete (5 seconds)
102
+ console.log('[eSCL] Waiting for scan to complete (5 seconds)...');
103
+ await new Promise((resolve) => setTimeout(resolve, 5000));
104
+ // 3. Download images via NextDocument endpoint
105
+ const filePaths = [];
106
+ let pageNum = 1;
107
+ const isAdf = source === 'Feeder';
108
+ while (true) {
109
+ console.log(`[eSCL] Attempting to download page ${pageNum}...`);
110
+ const imageBuffer = await downloadNextDocument(jobUrl);
111
+ if (!imageBuffer) {
112
+ // 404 or error
113
+ if (pageNum === 1) {
114
+ // First page failed
115
+ throw new Error('Failed to download scan result. Make sure document is loaded in scanner.');
116
+ }
117
+ else {
118
+ // Subsequent pages: 404 is normal (scan complete)
119
+ console.log(`[eSCL] Scan completed: ${pageNum - 1} pages`);
120
+ break;
121
+ }
122
+ }
123
+ // Successfully got image data
124
+ console.log(`[eSCL] Page ${pageNum} downloaded: ${imageBuffer.length} bytes`);
125
+ // Save image to file
126
+ const filePath = await saveImageToFile(savePath, imageBuffer, pageNum);
127
+ filePaths.push(filePath);
128
+ // Platen (flatbed) only has 1 page
129
+ if (!isAdf) {
130
+ break;
131
+ }
132
+ pageNum++;
133
+ }
134
+ // 4. Delete scan job (cleanup)
135
+ await deleteScanJob(jobUrl);
136
+ return filePaths.length > 0 ? filePaths : null;
137
+ }
138
+ catch (error) {
139
+ console.error('Quick scan failed:', error);
140
+ return null;
141
+ }
142
+ }
143
+ /**
144
+ * Save image to file system
145
+ */
146
+ async function saveImageToFile(folderPath, imageBuffer, pageNum) {
147
+ return new Promise((resolve, reject) => {
148
+ const fs = require('fs');
149
+ const path = require('path');
150
+ try {
151
+ // Create folder if it doesn't exist
152
+ if (!fs.existsSync(folderPath)) {
153
+ fs.mkdirSync(folderPath, { recursive: true });
154
+ }
155
+ // Generate filename with timestamp
156
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
157
+ const fileName = `scan_${timestamp}_page${pageNum}.jpg`;
158
+ const filePath = path.join(folderPath, fileName);
159
+ // Write file synchronously
160
+ fs.writeFileSync(filePath, imageBuffer);
161
+ console.log(`[eSCL] Image saved: ${filePath}`);
162
+ resolve(filePath);
163
+ }
164
+ catch (error) {
165
+ reject(error);
166
+ }
167
+ });
168
+ }
169
+ /**
170
+ * Download next document from scan job
171
+ */
172
+ async function downloadNextDocument(jobUrl) {
173
+ try {
174
+ const nextDocUrl = `${jobUrl}/NextDocument`;
175
+ return await httpGetBinary(nextDocUrl);
176
+ }
177
+ catch (error) {
178
+ // 404 is expected when scan is complete
179
+ if (error.code === 404 || (error.message && error.message.includes('404'))) {
180
+ return null;
181
+ }
182
+ console.error('[eSCL] Failed to download next document:', error);
183
+ return null;
184
+ }
185
+ }
186
+ /**
187
+ * Delete scan job (cleanup)
188
+ */
189
+ async function deleteScanJob(jobUrl) {
190
+ try {
191
+ await httpDelete(jobUrl);
192
+ console.log('[eSCL] Scan job deleted');
193
+ }
194
+ catch (error) {
195
+ // Failure to delete is not critical
196
+ console.log('[eSCL] Scan job delete attempted (already deleted by scanner)');
197
+ }
198
+ }
199
+ /**
200
+ * HTTP GET binary helper
201
+ */
202
+ function httpGetBinary(url, timeout = 10000) {
203
+ return new Promise((resolve, reject) => {
204
+ const req = require('http').get(url, { timeout }, (res) => {
205
+ const chunks = [];
206
+ res.on('data', (chunk) => {
207
+ chunks.push(chunk);
208
+ });
209
+ res.on('end', () => {
210
+ if (res.statusCode === 200) {
211
+ resolve(Buffer.concat(chunks));
212
+ }
213
+ else {
214
+ const error = new Error(`HTTP ${res.statusCode}`);
215
+ error.code = res.statusCode;
216
+ reject(error);
217
+ }
218
+ });
219
+ });
220
+ req.on('timeout', () => {
221
+ req.destroy();
222
+ reject(new Error('Request timeout'));
223
+ });
224
+ req.on('error', reject);
225
+ });
226
+ }
227
+ /**
228
+ * HTTP DELETE helper
229
+ */
230
+ function httpDelete(url, timeout = 10000) {
231
+ return new Promise((resolve, reject) => {
232
+ const options = {
233
+ method: 'DELETE',
234
+ timeout
235
+ };
236
+ const req = require('http').request(url, options, (res) => {
237
+ res.on('data', () => { });
238
+ res.on('end', () => {
239
+ resolve();
240
+ });
241
+ });
242
+ req.on('timeout', () => {
243
+ req.destroy();
244
+ reject(new Error('Request timeout'));
245
+ });
246
+ req.on('error', reject);
247
+ req.end();
248
+ });
249
+ }
250
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}