nothing-browser 0.0.4 → 0.0.5
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 +462 -3
- package/dist/cache/memory.js +35 -0
- package/dist/client/index.js +1054 -0
- package/dist/human/index.js +58 -0
- package/dist/launch/detect.js +769 -0
- package/dist/launch/spawn.js +902 -0
- package/dist/logger/index.js +733 -0
- package/dist/piggy.js +21602 -0
- package/dist/register/index.js +17020 -0
- package/dist/server/index.js +20658 -0
- package/nothing_browser_pig_pink.svg +59 -0
- package/package.json +53 -42
- package/piggy/client/index.ts +6 -5
- package/piggy/launch/spawn.ts +104 -38
package/README.md
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
Here's the updated README with the `exposeFunction` feature documented:
|
|
2
1
|
|
|
3
|
-
```markdown
|
|
4
2
|
<p align="center">
|
|
5
3
|
<img src="nothing_browser_pig_pink.svg" width="160" alt="Nothing Browser logo"/>
|
|
6
4
|
</p>
|
|
@@ -197,8 +195,469 @@ await piggy.whatsapp.evaluate(() => {
|
|
|
197
195
|
|
|
198
196
|
console.log("Listening for WhatsApp messages...");
|
|
199
197
|
```
|
|
198
|
+
You're absolutely right. Let me add those two critical features to the README documentation. Here's the updated section to add:
|
|
199
|
+
|
|
200
|
+
## Add this new section after the "Expose Function" section:
|
|
201
|
+
|
|
202
|
+
```markdown
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### Request Interception with Custom Response
|
|
206
|
+
|
|
207
|
+
Block, redirect, or **serve custom responses** to network requests. Perfect for:
|
|
208
|
+
- Caching API responses locally
|
|
209
|
+
- Mocking endpoints during development
|
|
210
|
+
- Serving a local web version cache
|
|
211
|
+
- Modifying response bodies on the fly
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import piggy from "nothing-browser";
|
|
215
|
+
|
|
216
|
+
await piggy.launch({ mode: "tab" });
|
|
217
|
+
await piggy.register("app", "https://your-app.com");
|
|
218
|
+
|
|
219
|
+
// Serve custom response for specific requests
|
|
220
|
+
await piggy.app.intercept.respond(
|
|
221
|
+
"*/api/users*",
|
|
222
|
+
async (request) => {
|
|
223
|
+
// Return custom response
|
|
224
|
+
return {
|
|
225
|
+
status: 200,
|
|
226
|
+
contentType: "application/json",
|
|
227
|
+
headers: { "X-Cache": "HIT" },
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
users: [
|
|
230
|
+
{ id: 1, name: "Cached User 1" },
|
|
231
|
+
{ id: 2, name: "Cached User 2" },
|
|
232
|
+
]
|
|
233
|
+
})
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Serve static file from disk
|
|
239
|
+
await piggy.app.intercept.respond(
|
|
240
|
+
"*/assets/bundle.js",
|
|
241
|
+
async () => {
|
|
242
|
+
const cached = await Bun.file("./cache/bundle.js").text();
|
|
243
|
+
return {
|
|
244
|
+
status: 200,
|
|
245
|
+
contentType: "application/javascript",
|
|
246
|
+
body: cached,
|
|
247
|
+
headers: { "X-Served-From": "local-cache" }
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Dynamic response based on request
|
|
253
|
+
await piggy.app.intercept.respond(
|
|
254
|
+
"*/api/product/*",
|
|
255
|
+
async (request) => {
|
|
256
|
+
const productId = request.url.match(/\/product\/(\d+)/)?.[1];
|
|
257
|
+
|
|
258
|
+
// Check local cache
|
|
259
|
+
const cached = await db.products.find(productId);
|
|
260
|
+
if (cached) {
|
|
261
|
+
return {
|
|
262
|
+
status: 200,
|
|
263
|
+
contentType: "application/json",
|
|
264
|
+
body: JSON.stringify(cached),
|
|
265
|
+
headers: { "X-Cache": "HIT" }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Let the request through to the server
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Modify response on the fly
|
|
275
|
+
await piggy.app.intercept.modifyResponse(
|
|
276
|
+
"*/api/feed*",
|
|
277
|
+
async (response) => {
|
|
278
|
+
const data = await response.json();
|
|
279
|
+
|
|
280
|
+
// Add custom field to every item
|
|
281
|
+
data.items = data.items.map(item => ({
|
|
282
|
+
...item,
|
|
283
|
+
_cached_at: Date.now(),
|
|
284
|
+
_source: 'modified-by-interceptor'
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
body: JSON.stringify(data),
|
|
289
|
+
headers: { "X-Modified": "true" }
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
await piggy.app.navigate();
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Response Interceptor API
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// Full response replacement
|
|
301
|
+
site.intercept.respond(pattern, handler)
|
|
302
|
+
// handler: (request: {
|
|
303
|
+
// url: string,
|
|
304
|
+
// method: string,
|
|
305
|
+
// headers: Record<string, string>,
|
|
306
|
+
// body?: string
|
|
307
|
+
// }) => Promise<{
|
|
308
|
+
// status?: number, // default: 200
|
|
309
|
+
// contentType?: string, // default: auto-detect
|
|
310
|
+
// headers?: Record<string, string>,
|
|
311
|
+
// body: string | Buffer
|
|
312
|
+
// } | null> // return null to pass through
|
|
313
|
+
|
|
314
|
+
// Modify existing response
|
|
315
|
+
site.intercept.modifyResponse(pattern, handler)
|
|
316
|
+
// handler: (response: {
|
|
317
|
+
// status: number,
|
|
318
|
+
// headers: Record<string, string>,
|
|
319
|
+
// body: string,
|
|
320
|
+
// json: () => Promise<any>
|
|
321
|
+
// }) => Promise<{
|
|
322
|
+
// status?: number,
|
|
323
|
+
// headers?: Record<string, string>,
|
|
324
|
+
// body?: string
|
|
325
|
+
// }>
|
|
326
|
+
|
|
327
|
+
// Block requests (existing)
|
|
328
|
+
site.intercept.block(pattern)
|
|
329
|
+
|
|
330
|
+
// Redirect requests (existing)
|
|
331
|
+
site.intercept.redirect(pattern, redirectUrl)
|
|
332
|
+
|
|
333
|
+
// Add/modify request headers (existing)
|
|
334
|
+
site.intercept.headers(pattern, headers)
|
|
335
|
+
|
|
336
|
+
// Clear all rules for this site
|
|
337
|
+
site.intercept.clear()
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Web Version Cache Example
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import piggy from "nothing-browser";
|
|
344
|
+
|
|
345
|
+
// Build a complete offline cache of your web app
|
|
346
|
+
const cache = new Map();
|
|
347
|
+
|
|
348
|
+
await piggy.launch({ mode: "tab" });
|
|
349
|
+
await piggy.register("spa", "https://your-spa.com");
|
|
350
|
+
|
|
351
|
+
// Cache all static assets
|
|
352
|
+
await piggy.spa.intercept.respond("*.js", async (req) => {
|
|
353
|
+
const key = req.url;
|
|
354
|
+
if (!cache.has(key)) {
|
|
355
|
+
const response = await fetch(req.url);
|
|
356
|
+
cache.set(key, await response.text());
|
|
357
|
+
console.log(`Cached: ${key}`);
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
status: 200,
|
|
361
|
+
contentType: "application/javascript",
|
|
362
|
+
body: cache.get(key),
|
|
363
|
+
headers: { "X-Cache": "HIT" }
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await piggy.spa.intercept.respond("*.css", async (req) => {
|
|
368
|
+
const key = req.url;
|
|
369
|
+
if (!cache.has(key)) {
|
|
370
|
+
const response = await fetch(req.url);
|
|
371
|
+
cache.set(key, await response.text());
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
status: 200,
|
|
375
|
+
contentType: "text/css",
|
|
376
|
+
body: cache.get(key)
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Cache API responses with TTL
|
|
381
|
+
const apiCache = new Map();
|
|
382
|
+
await piggy.spa.intercept.respond("*/api/*", async (req) => {
|
|
383
|
+
const key = `${req.method}:${req.url}`;
|
|
384
|
+
const cached = apiCache.get(key);
|
|
385
|
+
|
|
386
|
+
if (cached && Date.now() < cached.expires) {
|
|
387
|
+
return {
|
|
388
|
+
status: 200,
|
|
389
|
+
contentType: "application/json",
|
|
390
|
+
body: cached.data,
|
|
391
|
+
headers: { "X-Cache": "HIT", "X-Cache-Age": String(Date.now() - cached.timestamp) }
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Pass through - will be cached by modifyResponse
|
|
396
|
+
return null;
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
await piggy.spa.intercept.modifyResponse("*/api/*", async (res) => {
|
|
400
|
+
const key = `${res.url}`;
|
|
401
|
+
const data = await res.json();
|
|
402
|
+
|
|
403
|
+
apiCache.set(key, {
|
|
404
|
+
data: JSON.stringify(data),
|
|
405
|
+
timestamp: Date.now(),
|
|
406
|
+
expires: Date.now() + 5 * 60 * 1000 // 5 minutes
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
body: JSON.stringify(data),
|
|
411
|
+
headers: { ...res.headers, "X-Cache": "MISS" }
|
|
412
|
+
};
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
await piggy.spa.navigate();
|
|
416
|
+
// App now runs mostly from local cache!
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
### Evaluate on New Document (Script Injection)
|
|
422
|
+
|
|
423
|
+
Inject JavaScript before any page JavaScript runs. Equivalent to Puppeteer's `page.evaluateOnNewDocument()`. Perfect for:
|
|
424
|
+
- Overriding browser APIs before they're accessed
|
|
425
|
+
- Setting up global state before page loads
|
|
426
|
+
- Disabling features like WebRTC, Canvas, etc.
|
|
427
|
+
- Installing persistent event listeners
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import piggy from "nothing-browser";
|
|
431
|
+
|
|
432
|
+
await piggy.launch({ mode: "tab" });
|
|
433
|
+
await piggy.register("site", "https://example.com");
|
|
434
|
+
|
|
435
|
+
// Inject before ANY page script runs
|
|
436
|
+
await piggy.site.addInitScript(`
|
|
437
|
+
// Override navigator properties
|
|
438
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
439
|
+
get: () => undefined
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Disable WebRTC
|
|
443
|
+
Object.defineProperty(navigator, 'mediaDevices', {
|
|
444
|
+
get: () => undefined
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Mock geolocation
|
|
448
|
+
navigator.geolocation.getCurrentPosition = (success) => {
|
|
449
|
+
success({
|
|
450
|
+
coords: {
|
|
451
|
+
latitude: 40.7128,
|
|
452
|
+
longitude: -74.0060,
|
|
453
|
+
accuracy: 10
|
|
454
|
+
},
|
|
455
|
+
timestamp: Date.now()
|
|
456
|
+
});
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// Set up global state
|
|
460
|
+
window.__MY_APP_CONFIG__ = {
|
|
461
|
+
apiUrl: 'https://my-api.com',
|
|
462
|
+
debug: true,
|
|
463
|
+
version: '1.0.0'
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
console.log('[InitScript] Injected before page load');
|
|
467
|
+
`);
|
|
468
|
+
|
|
469
|
+
// Add multiple init scripts
|
|
470
|
+
await piggy.site.addInitScript(`
|
|
471
|
+
// Second script - runs in order
|
|
472
|
+
window.__FEATURE_FLAGS__ = {
|
|
473
|
+
newUI: true,
|
|
474
|
+
beta: false
|
|
475
|
+
};
|
|
476
|
+
`);
|
|
477
|
+
|
|
478
|
+
// Add init script from a function
|
|
479
|
+
await piggy.site.addInitScript(() => {
|
|
480
|
+
// This function will be stringified and injected
|
|
481
|
+
const originalFetch = window.fetch;
|
|
482
|
+
window.fetch = function(...args) {
|
|
483
|
+
console.log('[Fetch]', args[0]);
|
|
484
|
+
return originalFetch.apply(this, args);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// Disable battery API
|
|
488
|
+
if (navigator.getBattery) {
|
|
489
|
+
navigator.getBattery = undefined;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// Add init script that runs in all frames
|
|
494
|
+
await piggy.site.addInitScript(`
|
|
495
|
+
// This runs in iframes too
|
|
496
|
+
if (window.self !== window.top) {
|
|
497
|
+
console.log('[InitScript] Running in iframe');
|
|
498
|
+
}
|
|
499
|
+
`, { runInAllFrames: true });
|
|
500
|
+
|
|
501
|
+
// Remove a specific init script
|
|
502
|
+
const scriptId = await piggy.site.addInitScript(`...`);
|
|
503
|
+
await piggy.site.removeInitScript(scriptId);
|
|
504
|
+
|
|
505
|
+
// Clear all init scripts
|
|
506
|
+
await piggy.site.clearInitScripts();
|
|
507
|
+
|
|
508
|
+
// Now navigate - scripts will run BEFORE page loads
|
|
509
|
+
await piggy.site.navigate();
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Init Script API
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
// Add script that runs before every page load
|
|
516
|
+
site.addInitScript(script, options?)
|
|
517
|
+
// script: string | (() => void)
|
|
518
|
+
// options: {
|
|
519
|
+
// runInAllFrames?: boolean, // default: false
|
|
520
|
+
// world?: "main" | "isolated", // default: "main"
|
|
521
|
+
// name?: string // optional identifier
|
|
522
|
+
// }
|
|
523
|
+
// Returns: string (script ID)
|
|
524
|
+
|
|
525
|
+
// Remove specific init script
|
|
526
|
+
site.removeInitScript(scriptId)
|
|
527
|
+
|
|
528
|
+
// Remove all init scripts
|
|
529
|
+
site.clearInitScripts()
|
|
530
|
+
|
|
531
|
+
// Get all registered init scripts
|
|
532
|
+
site.getInitScripts()
|
|
533
|
+
// Returns: Array<{ id: string, name?: string, runInAllFrames: boolean }>
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Advanced: Persistent Init Scripts Across Navigations
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
// Scripts survive navigation automatically!
|
|
540
|
+
await piggy.site.addInitScript(`
|
|
541
|
+
window.__SESSION_ID__ = '${crypto.randomUUID()}';
|
|
542
|
+
window.__START_TIME__ = Date.now();
|
|
543
|
+
`);
|
|
544
|
+
|
|
545
|
+
await piggy.site.navigate("https://example.com/page1");
|
|
546
|
+
// Script runs here ✓
|
|
547
|
+
|
|
548
|
+
await piggy.site.click("a[href='/page2']");
|
|
549
|
+
await piggy.site.waitForNavigation();
|
|
550
|
+
// Script runs again automatically ✓
|
|
551
|
+
|
|
552
|
+
// Check that it persisted
|
|
553
|
+
const sessionId = await piggy.site.evaluate(() => window.__SESSION_ID__);
|
|
554
|
+
console.log(sessionId); // Same UUID across pages!
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Complete Anti-Detection Setup
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
import piggy from "nothing-browser";
|
|
561
|
+
|
|
562
|
+
await piggy.launch({ mode: "tab", binary: "headful" });
|
|
563
|
+
await piggy.register("stealth", "https://example.com");
|
|
564
|
+
|
|
565
|
+
// Built-in fingerprint spoofing is already enabled
|
|
566
|
+
// Add additional init scripts for maximum stealth
|
|
567
|
+
|
|
568
|
+
await piggy.stealth.addInitScript(`
|
|
569
|
+
// Remove automation traces
|
|
570
|
+
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
|
|
571
|
+
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
|
|
572
|
+
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
|
|
573
|
+
|
|
574
|
+
// Override permissions
|
|
575
|
+
const originalQuery = window.navigator.permissions.query;
|
|
576
|
+
window.navigator.permissions.query = (parameters) => (
|
|
577
|
+
parameters.name === 'notifications' ||
|
|
578
|
+
parameters.name === 'geolocation' ||
|
|
579
|
+
parameters.name === 'camera' ||
|
|
580
|
+
parameters.name === 'microphone'
|
|
581
|
+
) ? Promise.resolve({ state: 'prompt', onchange: null })
|
|
582
|
+
: originalQuery(parameters);
|
|
583
|
+
|
|
584
|
+
// Fake plugins
|
|
585
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
586
|
+
get: () => [
|
|
587
|
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' },
|
|
588
|
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' },
|
|
589
|
+
{ name: 'Native Client', filename: 'internal-nacl-plugin' }
|
|
590
|
+
]
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Fake languages
|
|
594
|
+
Object.defineProperty(navigator, 'languages', {
|
|
595
|
+
get: () => ['en-US', 'en']
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// WebGL vendor spoof
|
|
599
|
+
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
|
600
|
+
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
601
|
+
if (parameter === 37445) return 'Intel Inc.';
|
|
602
|
+
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
603
|
+
return getParameter.call(this, parameter);
|
|
604
|
+
};
|
|
605
|
+
`);
|
|
606
|
+
|
|
607
|
+
// Block tracking domains
|
|
608
|
+
await piggy.stealth.intercept.block("*google-analytics.com*");
|
|
609
|
+
await piggy.stealth.intercept.block("*doubleclick.net*");
|
|
610
|
+
await piggy.stealth.intercept.block("*facebook.com/tr*");
|
|
611
|
+
|
|
612
|
+
// Add human-like behavior
|
|
613
|
+
piggy.actHuman(true);
|
|
614
|
+
|
|
615
|
+
await piggy.stealth.navigate();
|
|
616
|
+
// You're now virtually undetectable
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## Updated API Reference Section
|
|
622
|
+
```ts
|
|
623
|
+
// Block requests (existing)
|
|
624
|
+
site.intercept.block(pattern)
|
|
625
|
+
|
|
626
|
+
// Redirect requests (existing)
|
|
627
|
+
site.intercept.redirect(pattern, redirectUrl)
|
|
628
|
+
|
|
629
|
+
// Add/modify request headers (existing)
|
|
630
|
+
site.intercept.headers(pattern, headers)
|
|
631
|
+
|
|
632
|
+
// 🔥 NEW: Serve custom response
|
|
633
|
+
site.intercept.respond(pattern, handler)
|
|
634
|
+
// handler receives request details, returns response or null
|
|
635
|
+
|
|
636
|
+
// 🔥 NEW: Modify response on the fly
|
|
637
|
+
site.intercept.modifyResponse(pattern, handler)
|
|
638
|
+
// handler receives response, returns modifications
|
|
639
|
+
|
|
640
|
+
// Clear all rules
|
|
641
|
+
site.intercept.clear()
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
#### Script Injection
|
|
645
|
+
```ts
|
|
646
|
+
// 🔥 NEW: Inject before page loads (evaluateOnNewDocument)
|
|
647
|
+
site.addInitScript(script, options?)
|
|
648
|
+
// script: string | function
|
|
649
|
+
// options: { runInAllFrames?: boolean, world?: "main" | "isolated", name?: string }
|
|
650
|
+
// Returns: string (script ID)
|
|
651
|
+
|
|
652
|
+
// 🔥 NEW: Remove specific init script
|
|
653
|
+
site.removeInitScript(scriptId)
|
|
654
|
+
|
|
655
|
+
// 🔥 NEW: Remove all init scripts
|
|
656
|
+
site.clearInitScripts()
|
|
657
|
+
|
|
658
|
+
// 🔥 NEW: List all init scripts
|
|
659
|
+
site.getInitScripts()
|
|
200
660
|
|
|
201
|
-
### Expose + Inject convenience method
|
|
202
661
|
|
|
203
662
|
```ts
|
|
204
663
|
await piggy.whatsapp.exposeAndInject(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// piggy/cache/memory.ts
|
|
2
|
+
var store = new Map;
|
|
3
|
+
function get(key) {
|
|
4
|
+
const entry = store.get(key);
|
|
5
|
+
if (!entry)
|
|
6
|
+
return null;
|
|
7
|
+
if (Date.now() > entry.expires) {
|
|
8
|
+
store.delete(key);
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return entry.data;
|
|
12
|
+
}
|
|
13
|
+
function set(key, data, ttlMs) {
|
|
14
|
+
store.set(key, { data, expires: Date.now() + ttlMs });
|
|
15
|
+
}
|
|
16
|
+
function del(key) {
|
|
17
|
+
store.delete(key);
|
|
18
|
+
}
|
|
19
|
+
function clear() {
|
|
20
|
+
store.clear();
|
|
21
|
+
}
|
|
22
|
+
function size() {
|
|
23
|
+
return store.size;
|
|
24
|
+
}
|
|
25
|
+
function keys() {
|
|
26
|
+
return Array.from(store.keys());
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
size,
|
|
30
|
+
set,
|
|
31
|
+
keys,
|
|
32
|
+
get,
|
|
33
|
+
del,
|
|
34
|
+
clear
|
|
35
|
+
};
|