phoneshield 1.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/LICENSE +21 -0
- package/README.md +430 -0
- package/dist/au.d.ts +7 -0
- package/dist/au.js +4 -0
- package/dist/au.js.map +1 -0
- package/dist/ca.d.ts +7 -0
- package/dist/ca.js +4 -0
- package/dist/ca.js.map +1 -0
- package/dist/chunk-5TMYOORT.js +40 -0
- package/dist/chunk-5TMYOORT.js.map +1 -0
- package/dist/chunk-7S4ACFFA.js +45 -0
- package/dist/chunk-7S4ACFFA.js.map +1 -0
- package/dist/chunk-DCA72IND.js +47 -0
- package/dist/chunk-DCA72IND.js.map +1 -0
- package/dist/chunk-KG5T34UR.js +196 -0
- package/dist/chunk-KG5T34UR.js.map +1 -0
- package/dist/chunk-NEZI6CET.js +38 -0
- package/dist/chunk-NEZI6CET.js.map +1 -0
- package/dist/chunk-R4Y2UNHW.js +40 -0
- package/dist/chunk-R4Y2UNHW.js.map +1 -0
- package/dist/chunk-UHUCGGQT.js +47 -0
- package/dist/chunk-UHUCGGQT.js.map +1 -0
- package/dist/chunk-ZHU56JDV.js +38 -0
- package/dist/chunk-ZHU56JDV.js.map +1 -0
- package/dist/chunk-ZN7XDKND.js +40 -0
- package/dist/chunk-ZN7XDKND.js.map +1 -0
- package/dist/de.d.ts +7 -0
- package/dist/de.js +4 -0
- package/dist/de.js.map +1 -0
- package/dist/fr.d.ts +7 -0
- package/dist/fr.js +4 -0
- package/dist/fr.js.map +1 -0
- package/dist/in.d.ts +7 -0
- package/dist/in.js +4 -0
- package/dist/in.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/jp.d.ts +7 -0
- package/dist/jp.js +4 -0
- package/dist/jp.js.map +1 -0
- package/dist/react.js +570 -0
- package/dist/react.js.map +1 -0
- package/dist/types-CbWYeYFP.d.ts +31 -0
- package/dist/uk.d.ts +7 -0
- package/dist/uk.js +4 -0
- package/dist/uk.js.map +1 -0
- package/dist/us.d.ts +7 -0
- package/dist/us.js +4 -0
- package/dist/us.js.map +1 -0
- package/package.json +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PhoneShield
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# PhoneShield
|
|
2
|
+
|
|
3
|
+
A lightning-fast, privacy-first phone intelligence engine. A smarter, tree-shakable alternative to `libphonenumber-js` and `numverify` — works everywhere JavaScript runs: **Node.js, browsers, React, Vue, Svelte, plain HTML**.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **< 5KB per country** — ESM-first, tree-shakable. Import only what you need.
|
|
8
|
+
- **Framework-agnostic** — Core engine has zero dependencies. Use it in Node, Deno, Bun, or any frontend framework.
|
|
9
|
+
- **Hybrid validation** — Local regex + length checks, no network calls required.
|
|
10
|
+
- **"Did You Mean?"** — Suggests corrections when a number is off by 1–2 digits.
|
|
11
|
+
- **Intelligence schema** — Returns `isValid`, E.164 `format`, `country`, `lineType`, and `riskScore`.
|
|
12
|
+
- **Privacy-first** — SHA-256 hashing for zero-knowledge spam database lookups.
|
|
13
|
+
- **Real-time formatting** — Built-in stateful formatter with debounce (framework-agnostic).
|
|
14
|
+
- **Optional React hook** — `usePhoneShield` available via `phoneshield/react` (React is never required).
|
|
15
|
+
- **TypeScript native** — Full type safety and IntelliSense out of the box.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install phoneshield
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Country-Specific Validation (Tree-Shakable)
|
|
26
|
+
|
|
27
|
+
Import only the country you need — each entry point pulls in just that country's metadata:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { validateUS } from 'phoneshield/us';
|
|
31
|
+
|
|
32
|
+
const result = validateUS('(202) 555-1234');
|
|
33
|
+
// {
|
|
34
|
+
// isValid: true,
|
|
35
|
+
// format: '+12025551234',
|
|
36
|
+
// country: 'US',
|
|
37
|
+
// lineType: 'Mobile',
|
|
38
|
+
// riskScore: 0.0
|
|
39
|
+
// }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Multi-Country
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { validateUS } from 'phoneshield/us';
|
|
46
|
+
import { validateUK } from 'phoneshield/uk';
|
|
47
|
+
import { validateFR } from 'phoneshield/fr';
|
|
48
|
+
import { validateIN } from 'phoneshield/in';
|
|
49
|
+
|
|
50
|
+
validateUS('2025551234');
|
|
51
|
+
validateUK('7400123456');
|
|
52
|
+
validateFR('612345678');
|
|
53
|
+
validateIN('9876543210');
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Generic Validation (Any Country)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { validate, getMetadata } from 'phoneshield';
|
|
60
|
+
|
|
61
|
+
const metadata = getMetadata('DE');
|
|
62
|
+
const result = validate('15112345678', metadata, {
|
|
63
|
+
enableSuggestions: true,
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Supported Countries
|
|
68
|
+
|
|
69
|
+
| Code | Country | Dial Code | Import Path |
|
|
70
|
+
| ---- | ------- | --------- | ----------- |
|
|
71
|
+
| US | United States | +1 | `phoneshield/us` |
|
|
72
|
+
| CA | Canada | +1 | `phoneshield/ca` |
|
|
73
|
+
| UK | United Kingdom | +44 | `phoneshield/uk` |
|
|
74
|
+
| AU | Australia | +61 | `phoneshield/au` |
|
|
75
|
+
| DE | Germany | +49 | `phoneshield/de` |
|
|
76
|
+
| FR | France | +33 | `phoneshield/fr` |
|
|
77
|
+
| JP | Japan | +81 | `phoneshield/jp` |
|
|
78
|
+
| IN | India | +91 | `phoneshield/in` |
|
|
79
|
+
|
|
80
|
+
## Validation Result Schema
|
|
81
|
+
|
|
82
|
+
Every validation call returns a `ValidationResult`:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface ValidationResult {
|
|
86
|
+
isValid: boolean; // Pass/fail
|
|
87
|
+
format: string | null; // E.164 format (e.g. "+12025551234")
|
|
88
|
+
country: CountryCode | null; // "US", "UK", "FR", etc.
|
|
89
|
+
lineType: LineType; // "Mobile" | "Landline" | "VoIP" | "TollFree" | "Premium" | "Unknown"
|
|
90
|
+
riskScore: number; // 0.0 (safe) to 1.0 (high risk)
|
|
91
|
+
suggestion?: string; // "Did you mean (202) 555-1234?"
|
|
92
|
+
errors?: string[]; // Human-readable error messages
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## "Did You Mean?" Engine
|
|
97
|
+
|
|
98
|
+
When a number is off by 1–2 digits, PhoneShield suggests the closest valid number:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { validateUS } from 'phoneshield/us';
|
|
102
|
+
|
|
103
|
+
const result = validateUS('202555123'); // 9 digits instead of 10
|
|
104
|
+
console.log(result.isValid); // false
|
|
105
|
+
console.log(result.suggestion); // "(202) 555-1234"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Risk Scoring
|
|
109
|
+
|
|
110
|
+
PhoneShield scores numbers from `0.0` (safe) to `1.0` (high risk) based on:
|
|
111
|
+
|
|
112
|
+
- **Invalid format/length** — +0.5
|
|
113
|
+
- **Premium numbers** (e.g. 900) — +0.3
|
|
114
|
+
- **VoIP numbers** — +0.2
|
|
115
|
+
- **Unknown line type** — +0.25
|
|
116
|
+
- **Repeating digits** (e.g. 5555555) — +0.15
|
|
117
|
+
- **Sequential patterns** (e.g. 123456) — +0.1
|
|
118
|
+
- **Suspicious prefixes** (000, 999) — +0.2
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { validateUS } from 'phoneshield/us';
|
|
122
|
+
|
|
123
|
+
const result = validateUS('9001234567');
|
|
124
|
+
console.log(result.riskScore); // 0.3 (Premium number)
|
|
125
|
+
console.log(result.lineType); // "Premium"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Line Type Detection
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { validateUS } from 'phoneshield/us';
|
|
132
|
+
|
|
133
|
+
validateUS('2025551234').lineType; // "Mobile" or "Landline"
|
|
134
|
+
validateUS('8005551234').lineType; // "TollFree"
|
|
135
|
+
validateUS('9005551234').lineType; // "Premium"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Privacy-First Hashing
|
|
139
|
+
|
|
140
|
+
Generate SHA-256 hashes to query spam databases without exposing the actual number:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { hashPhoneNumber } from 'phoneshield';
|
|
144
|
+
|
|
145
|
+
const hash = await hashPhoneNumber('+12025551234');
|
|
146
|
+
// "a3f2b8c1..." — send this to your spam API, not the raw number
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Uses the Web Crypto API (browser + Node 18+). No external dependencies.
|
|
150
|
+
|
|
151
|
+
## Real-Time Formatting (Framework-Agnostic)
|
|
152
|
+
|
|
153
|
+
The `createPhoneFormatter()` engine works in **any** JavaScript environment — no React, no framework needed:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { createPhoneFormatter } from 'phoneshield';
|
|
157
|
+
|
|
158
|
+
const formatter = createPhoneFormatter('US', {
|
|
159
|
+
debounceMs: 300,
|
|
160
|
+
enableSuggestions: true,
|
|
161
|
+
onStateChange: (state) => {
|
|
162
|
+
console.log(state.formattedValue); // "(202) 555-1234"
|
|
163
|
+
console.log(state.validation); // ValidationResult or null
|
|
164
|
+
console.log(state.isValidating); // true/false
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Feed input as the user types
|
|
169
|
+
formatter.handleInput('2');
|
|
170
|
+
formatter.handleInput('20');
|
|
171
|
+
formatter.handleInput('202');
|
|
172
|
+
formatter.handleInput('2025551234');
|
|
173
|
+
|
|
174
|
+
// Read state at any time
|
|
175
|
+
const state = formatter.getState();
|
|
176
|
+
|
|
177
|
+
// Clean up
|
|
178
|
+
formatter.clear();
|
|
179
|
+
formatter.destroy();
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Usage with Vue
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
186
|
+
import { createPhoneFormatter } from 'phoneshield';
|
|
187
|
+
|
|
188
|
+
const formattedValue = ref('');
|
|
189
|
+
const validation = ref(null);
|
|
190
|
+
|
|
191
|
+
let formatter;
|
|
192
|
+
|
|
193
|
+
onMounted(() => {
|
|
194
|
+
formatter = createPhoneFormatter('FR', {
|
|
195
|
+
debounceMs: 250,
|
|
196
|
+
onStateChange: (state) => {
|
|
197
|
+
formattedValue.value = state.formattedValue;
|
|
198
|
+
validation.value = state.validation;
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
onUnmounted(() => formatter?.destroy());
|
|
204
|
+
|
|
205
|
+
function onInput(e) {
|
|
206
|
+
formatter?.handleInput(e.target.value);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Usage with Svelte
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { createPhoneFormatter } from 'phoneshield';
|
|
214
|
+
import { onDestroy } from 'svelte';
|
|
215
|
+
|
|
216
|
+
let formattedValue = '';
|
|
217
|
+
let validation = null;
|
|
218
|
+
|
|
219
|
+
const formatter = createPhoneFormatter('DE', {
|
|
220
|
+
onStateChange: (state) => {
|
|
221
|
+
formattedValue = state.formattedValue;
|
|
222
|
+
validation = state.validation;
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
onDestroy(() => formatter.destroy());
|
|
227
|
+
|
|
228
|
+
function handleInput(e) {
|
|
229
|
+
formatter.handleInput(e.target.value);
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Usage in Node.js / Backend
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { validateUS } from 'phoneshield/us';
|
|
237
|
+
import { hashPhoneNumber } from 'phoneshield';
|
|
238
|
+
|
|
239
|
+
// Validate incoming phone number
|
|
240
|
+
const result = validateUS(req.body.phone);
|
|
241
|
+
|
|
242
|
+
if (!result.isValid) {
|
|
243
|
+
return res.status(400).json({ errors: result.errors });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Store only the hash
|
|
247
|
+
const hash = await hashPhoneNumber(result.format);
|
|
248
|
+
await db.users.update({ phoneHash: hash });
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## React Hook (Optional)
|
|
252
|
+
|
|
253
|
+
Install React as usual — it's an **optional** peer dependency. Import from the dedicated subpath:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { usePhoneShield } from 'phoneshield/react';
|
|
257
|
+
|
|
258
|
+
function PhoneInput() {
|
|
259
|
+
const {
|
|
260
|
+
formattedValue,
|
|
261
|
+
validation,
|
|
262
|
+
isValidating,
|
|
263
|
+
handleChange,
|
|
264
|
+
clear,
|
|
265
|
+
} = usePhoneShield('US', { debounceMs: 300, enableSuggestions: true });
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div>
|
|
269
|
+
<input
|
|
270
|
+
type="tel"
|
|
271
|
+
value={formattedValue}
|
|
272
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
273
|
+
placeholder="(555) 123-4567"
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
{isValidating && <span>Validating...</span>}
|
|
277
|
+
|
|
278
|
+
{validation && (
|
|
279
|
+
<div>
|
|
280
|
+
<p>Valid: {validation.isValid ? 'Yes' : 'No'}</p>
|
|
281
|
+
<p>Type: {validation.lineType}</p>
|
|
282
|
+
<p>Risk: {(validation.riskScore * 100).toFixed(0)}%</p>
|
|
283
|
+
{validation.suggestion && (
|
|
284
|
+
<p>Did you mean: {validation.suggestion}?</p>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
|
|
289
|
+
<button onClick={clear}>Clear</button>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Formatting Utilities
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { normalizePhoneNumber, formatPhoneNumber, toE164 } from 'phoneshield';
|
|
299
|
+
import { US_METADATA } from 'phoneshield/us';
|
|
300
|
+
|
|
301
|
+
normalizePhoneNumber('(202) 555-1234');
|
|
302
|
+
// "2025551234"
|
|
303
|
+
|
|
304
|
+
formatPhoneNumber('2025551234', US_METADATA);
|
|
305
|
+
// "(202) 555-1234"
|
|
306
|
+
|
|
307
|
+
toE164('2025551234', US_METADATA);
|
|
308
|
+
// "+12025551234"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Custom Country Metadata
|
|
312
|
+
|
|
313
|
+
Add your own country by implementing the `CountryMetadata` interface:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { CountryMetadata, validate } from 'phoneshield';
|
|
317
|
+
|
|
318
|
+
const MY_METADATA: CountryMetadata = {
|
|
319
|
+
countryCode: 'MY' as any,
|
|
320
|
+
dialCode: '+60',
|
|
321
|
+
patterns: {
|
|
322
|
+
mobile: [/^1[0-46-9]\d{7,8}$/],
|
|
323
|
+
landline: [/^[3-9]\d{7}$/],
|
|
324
|
+
voip: [],
|
|
325
|
+
tollFree: [/^1800\d{6}$/],
|
|
326
|
+
premium: [],
|
|
327
|
+
},
|
|
328
|
+
lengths: [9, 10],
|
|
329
|
+
format: (digits) =>
|
|
330
|
+
digits.length === 10
|
|
331
|
+
? `${digits.slice(0, 3)}-${digits.slice(3, 6)} ${digits.slice(6)}`
|
|
332
|
+
: digits,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const result = validate('123456789', MY_METADATA);
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## API Reference
|
|
339
|
+
|
|
340
|
+
### Core
|
|
341
|
+
|
|
342
|
+
| Function | Description |
|
|
343
|
+
| -------- | ----------- |
|
|
344
|
+
| `validate(input, metadata, options?)` | Full validation with intelligence schema |
|
|
345
|
+
| `validateUS(phone, options?)` | US-specific (tree-shakable) |
|
|
346
|
+
| `validateUK(phone, options?)` | UK-specific |
|
|
347
|
+
| `validateCA(phone, options?)` | Canada-specific |
|
|
348
|
+
| `validateAU(phone, options?)` | Australia-specific |
|
|
349
|
+
| `validateDE(phone, options?)` | Germany-specific |
|
|
350
|
+
| `validateFR(phone, options?)` | France-specific |
|
|
351
|
+
| `validateJP(phone, options?)` | Japan-specific |
|
|
352
|
+
| `validateIN(phone, options?)` | India-specific |
|
|
353
|
+
| `getMetadata(country)` | Get metadata for a country code |
|
|
354
|
+
|
|
355
|
+
### Formatting
|
|
356
|
+
|
|
357
|
+
| Function | Description |
|
|
358
|
+
| -------- | ----------- |
|
|
359
|
+
| `normalizePhoneNumber(input)` | Strip all non-digit characters |
|
|
360
|
+
| `formatPhoneNumber(digits, metadata)` | Format to local display format |
|
|
361
|
+
| `toE164(digits, metadata)` | Format to E.164 international format |
|
|
362
|
+
|
|
363
|
+
### Real-Time Formatter
|
|
364
|
+
|
|
365
|
+
| Function | Description |
|
|
366
|
+
| -------- | ----------- |
|
|
367
|
+
| `createPhoneFormatter(country?, options?)` | Create a stateful formatter instance |
|
|
368
|
+
|
|
369
|
+
Returns a `PhoneFormatter` with:
|
|
370
|
+
|
|
371
|
+
- `handleInput(input)` — Process new input
|
|
372
|
+
- `getState()` — Get current `{ value, formattedValue, validation, isValidating }`
|
|
373
|
+
- `clear()` — Reset state
|
|
374
|
+
- `destroy()` — Clean up timers
|
|
375
|
+
|
|
376
|
+
### Privacy
|
|
377
|
+
|
|
378
|
+
| Function | Description |
|
|
379
|
+
| -------- | ----------- |
|
|
380
|
+
| `hashPhoneNumber(phone)` | SHA-256 hash (async, returns hex string) |
|
|
381
|
+
| `createZKProof(phone)` | Alias for `hashPhoneNumber` |
|
|
382
|
+
|
|
383
|
+
### React (Optional)
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
import { usePhoneShield } from 'phoneshield/react';
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
| Hook | Description |
|
|
390
|
+
| ---- | ----------- |
|
|
391
|
+
| `usePhoneShield(country?, options?)` | Real-time formatting + validation hook |
|
|
392
|
+
|
|
393
|
+
Returns `{ value, formattedValue, validation, isValidating, handleChange, clear }`.
|
|
394
|
+
|
|
395
|
+
## Options
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
interface PhoneShieldOptions {
|
|
399
|
+
defaultCountry?: CountryCode; // Fallback country
|
|
400
|
+
strictMode?: boolean; // Stricter pattern matching
|
|
401
|
+
enableSuggestions?: boolean; // Enable "Did You Mean?" (default: true)
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
For `createPhoneFormatter` and `usePhoneShield`, you can also pass:
|
|
406
|
+
|
|
407
|
+
- `debounceMs` — Debounce delay in ms (default: `300`)
|
|
408
|
+
|
|
409
|
+
## Bundle Size
|
|
410
|
+
|
|
411
|
+
| Import | Size |
|
|
412
|
+
| ------ | ---- |
|
|
413
|
+
| `phoneshield/us` | ~1.2 KB |
|
|
414
|
+
| `phoneshield/fr` | ~1.0 KB |
|
|
415
|
+
| Any single country | < 2 KB |
|
|
416
|
+
| `phoneshield` (all countries) | ~3.5 KB |
|
|
417
|
+
| `phoneshield/react` | ~15 KB (includes core) |
|
|
418
|
+
|
|
419
|
+
Measured with `tsup` tree-shaking enabled. Actual sizes depend on your bundler.
|
|
420
|
+
|
|
421
|
+
## Compatibility
|
|
422
|
+
|
|
423
|
+
- **Node.js** 18+ (uses `crypto.subtle` for hashing)
|
|
424
|
+
- **Browsers**: Chrome 37+, Firefox 34+, Safari 11+, Edge 79+
|
|
425
|
+
- **Deno**, **Bun** — works out of the box
|
|
426
|
+
- **React** 17+ (optional, for `phoneshield/react` only)
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
MIT
|
package/dist/au.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { C as CountryMetadata, P as PhoneShieldOptions, V as ValidationResult } from './types-CbWYeYFP.js';
|
|
2
|
+
|
|
3
|
+
declare const AU_METADATA: CountryMetadata;
|
|
4
|
+
|
|
5
|
+
declare function validateAU(phoneNumber: string, options?: PhoneShieldOptions): ValidationResult;
|
|
6
|
+
|
|
7
|
+
export { AU_METADATA, validateAU };
|
package/dist/au.js
ADDED
package/dist/au.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"au.js"}
|
package/dist/ca.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { C as CountryMetadata, P as PhoneShieldOptions, V as ValidationResult } from './types-CbWYeYFP.js';
|
|
2
|
+
|
|
3
|
+
declare const CA_METADATA: CountryMetadata;
|
|
4
|
+
|
|
5
|
+
declare function validateCA(phoneNumber: string, options?: PhoneShieldOptions): ValidationResult;
|
|
6
|
+
|
|
7
|
+
export { CA_METADATA, validateCA };
|
package/dist/ca.js
ADDED
package/dist/ca.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"ca.js"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { validate } from './chunk-KG5T34UR.js';
|
|
2
|
+
|
|
3
|
+
// src/phoneshield/metadata/fr.ts
|
|
4
|
+
var FR_METADATA = {
|
|
5
|
+
countryCode: "FR",
|
|
6
|
+
dialCode: "+33",
|
|
7
|
+
patterns: {
|
|
8
|
+
mobile: [
|
|
9
|
+
/^[67]\d{8}$/
|
|
10
|
+
],
|
|
11
|
+
landline: [
|
|
12
|
+
/^[1-5]\d{8}$/
|
|
13
|
+
],
|
|
14
|
+
voip: [
|
|
15
|
+
/^9\d{8}$/
|
|
16
|
+
],
|
|
17
|
+
tollFree: [
|
|
18
|
+
/^80[0-5]\d{6}$/
|
|
19
|
+
],
|
|
20
|
+
premium: [
|
|
21
|
+
/^8[129]\d{7}$/
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
lengths: [9],
|
|
25
|
+
format: (digits) => {
|
|
26
|
+
if (digits.length === 9) {
|
|
27
|
+
return `${digits.slice(0, 1)} ${digits.slice(1, 3)} ${digits.slice(3, 5)} ${digits.slice(5, 7)} ${digits.slice(7)}`;
|
|
28
|
+
}
|
|
29
|
+
return digits;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/phoneshield/fr.ts
|
|
34
|
+
function validateFR(phoneNumber, options) {
|
|
35
|
+
return validate(phoneNumber, FR_METADATA, options);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { FR_METADATA, validateFR };
|
|
39
|
+
//# sourceMappingURL=chunk-5TMYOORT.js.map
|
|
40
|
+
//# sourceMappingURL=chunk-5TMYOORT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/phoneshield/metadata/fr.ts","../src/phoneshield/fr.ts"],"names":[],"mappings":";;;AAEO,IAAM,WAAA,GAA+B;AAAA,EAC1C,WAAA,EAAa,IAAA;AAAA,EACb,QAAA,EAAU,KAAA;AAAA,EACV,QAAA,EAAU;AAAA,IACR,MAAA,EAAQ;AAAA,MACN;AAAA,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,KACF;AAAA,IACA,IAAA,EAAM;AAAA,MACJ;AAAA,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,KACF;AAAA,IACA,OAAA,EAAS;AAAA,MACP;AAAA;AACF,GACF;AAAA,EACA,OAAA,EAAS,CAAC,CAAC,CAAA;AAAA,EACX,MAAA,EAAQ,CAAC,MAAA,KAAmB;AAC1B,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,MAAA,OAAO,CAAA,EAAG,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAAA,IACnH;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;ACzBO,SAAS,UAAA,CACd,aACA,OAAA,EACkB;AAClB,EAAA,OAAO,QAAA,CAAS,WAAA,EAAa,WAAA,EAAa,OAAO,CAAA;AACnD","file":"chunk-5TMYOORT.js","sourcesContent":["import { CountryMetadata } from '../types';\n\nexport const FR_METADATA: CountryMetadata = {\n countryCode: 'FR',\n dialCode: '+33',\n patterns: {\n mobile: [\n /^[67]\\d{8}$/,\n ],\n landline: [\n /^[1-5]\\d{8}$/,\n ],\n voip: [\n /^9\\d{8}$/,\n ],\n tollFree: [\n /^80[0-5]\\d{6}$/,\n ],\n premium: [\n /^8[129]\\d{7}$/,\n ],\n },\n lengths: [9],\n format: (digits: string) => {\n if (digits.length === 9) {\n return `${digits.slice(0, 1)} ${digits.slice(1, 3)} ${digits.slice(3, 5)} ${digits.slice(5, 7)} ${digits.slice(7)}`;\n }\n return digits;\n },\n};\n","import { ValidationResult, PhoneShieldOptions } from './types';\nimport { FR_METADATA } from './metadata/fr';\nimport { validate } from './core/validator';\n\nexport function validateFR(\n phoneNumber: string,\n options?: PhoneShieldOptions\n): ValidationResult {\n return validate(phoneNumber, FR_METADATA, options);\n}\n\nexport { FR_METADATA };\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { validate } from './chunk-KG5T34UR.js';
|
|
2
|
+
|
|
3
|
+
// src/phoneshield/metadata/de.ts
|
|
4
|
+
var DE_METADATA = {
|
|
5
|
+
countryCode: "DE",
|
|
6
|
+
dialCode: "+49",
|
|
7
|
+
patterns: {
|
|
8
|
+
mobile: [
|
|
9
|
+
/^1(?:5[0-25-9]|6[023]|7[0-9])\d{7,8}$/
|
|
10
|
+
],
|
|
11
|
+
landline: [
|
|
12
|
+
/^(?:2\d|3[2-9]|4[1-8]|5[1-9]|6[1-8]|7[1-9]|8[1-9]|9[1-9])\d{5,11}$/
|
|
13
|
+
],
|
|
14
|
+
voip: [
|
|
15
|
+
/^32\d{9}$/
|
|
16
|
+
],
|
|
17
|
+
tollFree: [
|
|
18
|
+
/^800\d{7}$/
|
|
19
|
+
],
|
|
20
|
+
premium: [
|
|
21
|
+
/^900\d{7}$/,
|
|
22
|
+
/^137\d{7}$/
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
lengths: [7, 8, 9, 10, 11, 12],
|
|
26
|
+
format: (digits) => {
|
|
27
|
+
if (digits.length >= 10 && digits.startsWith("1")) {
|
|
28
|
+
return `${digits.slice(0, 4)} ${digits.slice(4)}`;
|
|
29
|
+
}
|
|
30
|
+
if (digits.length >= 7) {
|
|
31
|
+
const areaLen = digits.length <= 8 ? 2 : digits.length <= 10 ? 3 : 4;
|
|
32
|
+
return `${digits.slice(0, areaLen)} ${digits.slice(areaLen)}`;
|
|
33
|
+
}
|
|
34
|
+
return digits;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/phoneshield/de.ts
|
|
39
|
+
function validateDE(phoneNumber, options) {
|
|
40
|
+
return validate(phoneNumber, DE_METADATA, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { DE_METADATA, validateDE };
|
|
44
|
+
//# sourceMappingURL=chunk-7S4ACFFA.js.map
|
|
45
|
+
//# sourceMappingURL=chunk-7S4ACFFA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/phoneshield/metadata/de.ts","../src/phoneshield/de.ts"],"names":[],"mappings":";;;AAEO,IAAM,WAAA,GAA+B;AAAA,EAC1C,WAAA,EAAa,IAAA;AAAA,EACb,QAAA,EAAU,KAAA;AAAA,EACV,QAAA,EAAU;AAAA,IACR,MAAA,EAAQ;AAAA,MACN;AAAA,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,KACF;AAAA,IACA,IAAA,EAAM;AAAA,MACJ;AAAA,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,KACF;AAAA,IACA,OAAA,EAAS;AAAA,MACP,YAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,SAAS,CAAC,CAAA,EAAG,GAAG,CAAA,EAAG,EAAA,EAAI,IAAI,EAAE,CAAA;AAAA,EAC7B,MAAA,EAAQ,CAAC,MAAA,KAAmB;AAC1B,IAAA,IAAI,OAAO,MAAA,IAAU,EAAA,IAAM,MAAA,CAAO,UAAA,CAAW,GAAG,CAAA,EAAG;AACjD,MAAA,OAAO,CAAA,EAAG,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAAA,IACjD;AACA,IAAA,IAAI,MAAA,CAAO,UAAU,CAAA,EAAG;AACtB,MAAA,MAAM,OAAA,GAAU,OAAO,MAAA,IAAU,CAAA,GAAI,IAAI,MAAA,CAAO,MAAA,IAAU,KAAK,CAAA,GAAI,CAAA;AACnE,MAAA,OAAO,CAAA,EAAG,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,OAAO,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,OAAO,CAAC,CAAA,CAAA;AAAA,IAC7D;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;AC9BO,SAAS,UAAA,CACd,aACA,OAAA,EACkB;AAClB,EAAA,OAAO,QAAA,CAAS,WAAA,EAAa,WAAA,EAAa,OAAO,CAAA;AACnD","file":"chunk-7S4ACFFA.js","sourcesContent":["import { CountryMetadata } from '../types';\n\nexport const DE_METADATA: CountryMetadata = {\n countryCode: 'DE',\n dialCode: '+49',\n patterns: {\n mobile: [\n /^1(?:5[0-25-9]|6[023]|7[0-9])\\d{7,8}$/,\n ],\n landline: [\n /^(?:2\\d|3[2-9]|4[1-8]|5[1-9]|6[1-8]|7[1-9]|8[1-9]|9[1-9])\\d{5,11}$/,\n ],\n voip: [\n /^32\\d{9}$/,\n ],\n tollFree: [\n /^800\\d{7}$/,\n ],\n premium: [\n /^900\\d{7}$/,\n /^137\\d{7}$/,\n ],\n },\n lengths: [7, 8, 9, 10, 11, 12],\n format: (digits: string) => {\n if (digits.length >= 10 && digits.startsWith('1')) {\n return `${digits.slice(0, 4)} ${digits.slice(4)}`;\n }\n if (digits.length >= 7) {\n const areaLen = digits.length <= 8 ? 2 : digits.length <= 10 ? 3 : 4;\n return `${digits.slice(0, areaLen)} ${digits.slice(areaLen)}`;\n }\n return digits;\n },\n};\n","import { ValidationResult, PhoneShieldOptions } from './types';\nimport { DE_METADATA } from './metadata/de';\nimport { validate } from './core/validator';\n\nexport function validateDE(\n phoneNumber: string,\n options?: PhoneShieldOptions\n): ValidationResult {\n return validate(phoneNumber, DE_METADATA, options);\n}\n\nexport { DE_METADATA };\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { validate } from './chunk-KG5T34UR.js';
|
|
2
|
+
|
|
3
|
+
// src/phoneshield/metadata/au.ts
|
|
4
|
+
var AU_METADATA = {
|
|
5
|
+
countryCode: "AU",
|
|
6
|
+
dialCode: "+61",
|
|
7
|
+
patterns: {
|
|
8
|
+
mobile: [
|
|
9
|
+
/^4\d{8}$/
|
|
10
|
+
],
|
|
11
|
+
landline: [
|
|
12
|
+
/^[2378]\d{8}$/
|
|
13
|
+
],
|
|
14
|
+
voip: [
|
|
15
|
+
/^550\d{6}$/
|
|
16
|
+
],
|
|
17
|
+
tollFree: [
|
|
18
|
+
/^1800\d{6}$/,
|
|
19
|
+
/^1300\d{6}$/
|
|
20
|
+
],
|
|
21
|
+
premium: [
|
|
22
|
+
/^19\d{7,8}$/
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
lengths: [9, 10],
|
|
26
|
+
format: (digits) => {
|
|
27
|
+
if (digits.length === 9 && digits.startsWith("4")) {
|
|
28
|
+
return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`;
|
|
29
|
+
}
|
|
30
|
+
if (digits.length === 9) {
|
|
31
|
+
return `(0${digits.slice(0, 1)}) ${digits.slice(1, 5)} ${digits.slice(5)}`;
|
|
32
|
+
}
|
|
33
|
+
if (digits.length === 10) {
|
|
34
|
+
return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`;
|
|
35
|
+
}
|
|
36
|
+
return digits;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/phoneshield/au.ts
|
|
41
|
+
function validateAU(phoneNumber, options) {
|
|
42
|
+
return validate(phoneNumber, AU_METADATA, options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { AU_METADATA, validateAU };
|
|
46
|
+
//# sourceMappingURL=chunk-DCA72IND.js.map
|
|
47
|
+
//# sourceMappingURL=chunk-DCA72IND.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/phoneshield/metadata/au.ts","../src/phoneshield/au.ts"],"names":[],"mappings":";;;AAEO,IAAM,WAAA,GAA+B;AAAA,EAC1C,WAAA,EAAa,IAAA;AAAA,EACb,QAAA,EAAU,KAAA;AAAA,EACV,QAAA,EAAU;AAAA,IACR,MAAA,EAAQ;AAAA,MACN;AAAA,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,KACF;AAAA,IACA,IAAA,EAAM;AAAA,MACJ;AAAA,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR,aAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,OAAA,EAAS;AAAA,MACP;AAAA;AACF,GACF;AAAA,EACA,OAAA,EAAS,CAAC,CAAA,EAAG,EAAE,CAAA;AAAA,EACf,MAAA,EAAQ,CAAC,MAAA,KAAmB;AAC1B,IAAA,IAAI,OAAO,MAAA,KAAW,CAAA,IAAK,MAAA,CAAO,UAAA,CAAW,GAAG,CAAA,EAAG;AACjD,MAAA,OAAO,GAAG,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAAA,IACvE;AACA,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,MAAA,OAAO,KAAK,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,EAAA,EAAK,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAAA,IAC1E;AACA,IAAA,IAAI,MAAA,CAAO,WAAW,EAAA,EAAI;AACxB,MAAA,OAAO,GAAG,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA;AAAA,IACvE;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;AChCO,SAAS,UAAA,CACd,aACA,OAAA,EACkB;AAClB,EAAA,OAAO,QAAA,CAAS,WAAA,EAAa,WAAA,EAAa,OAAO,CAAA;AACnD","file":"chunk-DCA72IND.js","sourcesContent":["import { CountryMetadata } from '../types';\n\nexport const AU_METADATA: CountryMetadata = {\n countryCode: 'AU',\n dialCode: '+61',\n patterns: {\n mobile: [\n /^4\\d{8}$/,\n ],\n landline: [\n /^[2378]\\d{8}$/,\n ],\n voip: [\n /^550\\d{6}$/,\n ],\n tollFree: [\n /^1800\\d{6}$/,\n /^1300\\d{6}$/,\n ],\n premium: [\n /^19\\d{7,8}$/,\n ],\n },\n lengths: [9, 10],\n format: (digits: string) => {\n if (digits.length === 9 && digits.startsWith('4')) {\n return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`;\n }\n if (digits.length === 9) {\n return `(0${digits.slice(0, 1)}) ${digits.slice(1, 5)} ${digits.slice(5)}`;\n }\n if (digits.length === 10) {\n return `${digits.slice(0, 4)} ${digits.slice(4, 7)} ${digits.slice(7)}`;\n }\n return digits;\n },\n};\n","import { ValidationResult, PhoneShieldOptions } from './types';\nimport { AU_METADATA } from './metadata/au';\nimport { validate } from './core/validator';\n\nexport function validateAU(\n phoneNumber: string,\n options?: PhoneShieldOptions\n): ValidationResult {\n return validate(phoneNumber, AU_METADATA, options);\n}\n\nexport { AU_METADATA };\n"]}
|