@ekaone/mask-phone 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 +852 -0
- package/dist/index.d.mts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eka Prasetia <ekaone3033@gmail.com> (https://prasetia.me)
|
|
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,852 @@
|
|
|
1
|
+
A lightweight, zero-dependency TypeScript library for masking phone numbers to protect personal information.
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@ekaone/mask-phone)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🔒 **Privacy Compliant** - Follows GDPR and data protection standards
|
|
10
|
+
- ✨ **Lightweight** - Under 2KB, zero dependencies
|
|
11
|
+
- 📦 **TypeScript** - Full type safety and IntelliSense support
|
|
12
|
+
- ⚙️ **Flexible** - Extensive customization options
|
|
13
|
+
- 🌍 **Universal** - Supports all international phone formats (US, UK, EU, Asia, etc.)
|
|
14
|
+
- 🎯 **Locale-Agnostic** - No assumptions about phone number format
|
|
15
|
+
- 🚀 **Simple API** - Easy to use with sensible defaults
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @ekaone/mask-phone
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
yarn add @ekaone/mask-phone
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add @ekaone/mask-phone
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { maskPhone } from '@ekaone/mask-phone';
|
|
35
|
+
|
|
36
|
+
maskPhone('1234567890');
|
|
37
|
+
// Output: '******7890'
|
|
38
|
+
|
|
39
|
+
maskPhone('+62 812 3456 7890');
|
|
40
|
+
// Output: '+*********7890'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage Examples
|
|
44
|
+
|
|
45
|
+
### Basic Usage
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { maskPhone } from '@ekaone/mask-phone';
|
|
49
|
+
|
|
50
|
+
// Default masking (shows last 4 digits)
|
|
51
|
+
maskPhone('1234567890');
|
|
52
|
+
// Output: '******7890'
|
|
53
|
+
|
|
54
|
+
// Accepts number input
|
|
55
|
+
maskPhone(1234567890);
|
|
56
|
+
// Output: '******7890'
|
|
57
|
+
|
|
58
|
+
// Auto-strips formatting
|
|
59
|
+
maskPhone('+1-234-567-8901');
|
|
60
|
+
// Output: '+*******8901'
|
|
61
|
+
|
|
62
|
+
// Preserves international prefix
|
|
63
|
+
maskPhone('+1234567890');
|
|
64
|
+
// Output: '+******7890'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Show Beginning Digits
|
|
68
|
+
|
|
69
|
+
Control how many digits to show at the start:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Show first 3 digits (country/area code)
|
|
73
|
+
maskPhone('628123456789', { showFirst: 3 });
|
|
74
|
+
// Output: '628*****6789'
|
|
75
|
+
|
|
76
|
+
// Show first 2 digits
|
|
77
|
+
maskPhone('+1234567890', { showFirst: 2 });
|
|
78
|
+
// Output: '+1*****7890'
|
|
79
|
+
|
|
80
|
+
// Show only first digit
|
|
81
|
+
maskPhone('1234567890', { showFirst: 1, showLast: 0 });
|
|
82
|
+
// Output: '1*********'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Show Ending Digits
|
|
86
|
+
|
|
87
|
+
Control how many digits to show at the end:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Show last 2 digits
|
|
91
|
+
maskPhone('1234567890', { showLast: 2 });
|
|
92
|
+
// Output: '********90'
|
|
93
|
+
|
|
94
|
+
// Show last 6 digits
|
|
95
|
+
maskPhone('628123456789', { showLast: 6 });
|
|
96
|
+
// Output: '******456789'
|
|
97
|
+
|
|
98
|
+
// Hide all digits (complete masking)
|
|
99
|
+
maskPhone('1234567890', { showFirst: 0, showLast: 0 });
|
|
100
|
+
// Output: '**********'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Using Aliases (showStart/showEnd)
|
|
104
|
+
|
|
105
|
+
Alternative names for clarity:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// showStart is alias for showFirst
|
|
109
|
+
maskPhone('1234567890', { showStart: 3 });
|
|
110
|
+
// Output: '123****7890'
|
|
111
|
+
|
|
112
|
+
// showEnd is alias for showLast
|
|
113
|
+
maskPhone('1234567890', { showEnd: 2 });
|
|
114
|
+
// Output: '********90'
|
|
115
|
+
|
|
116
|
+
// Can be mixed (showFirst takes priority)
|
|
117
|
+
maskPhone('1234567890', { showStart: 2, showEnd: 3 });
|
|
118
|
+
// Output: '12*****890'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Custom Mask Character
|
|
122
|
+
|
|
123
|
+
Change the masking character from the default `*`:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
maskPhone('1234567890', { maskChar: '•' });
|
|
127
|
+
// Output: '••••••7890'
|
|
128
|
+
|
|
129
|
+
maskPhone('1234567890', { maskChar: 'X' });
|
|
130
|
+
// Output: 'XXXXXX7890'
|
|
131
|
+
|
|
132
|
+
maskPhone('1234567890', { maskChar: '#' });
|
|
133
|
+
// Output: '######7890'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Preserve Original Formatting
|
|
137
|
+
|
|
138
|
+
Maintain spaces, dashes, parentheses, and other separators from the input:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Preserve US format
|
|
142
|
+
maskPhone('+1 (555) 123-4567', { preserveFormat: true });
|
|
143
|
+
// Output: '+* (***) ***-4567'
|
|
144
|
+
|
|
145
|
+
// Preserve international spacing
|
|
146
|
+
maskPhone('+62 812 3456 7890', { preserveFormat: true });
|
|
147
|
+
// Output: '+** *** **** 7890'
|
|
148
|
+
|
|
149
|
+
// Preserve dashes
|
|
150
|
+
maskPhone('+1-555-123-4567', { preserveFormat: true });
|
|
151
|
+
// Output: '+*-***-***-4567'
|
|
152
|
+
|
|
153
|
+
// Preserve dots
|
|
154
|
+
maskPhone('+62.812.3456.7890', { preserveFormat: true });
|
|
155
|
+
// Output: '+**.***.****.7890'
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Visible Ranges
|
|
159
|
+
|
|
160
|
+
Show specific character ranges:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// Show country code and last 4
|
|
164
|
+
maskPhone('628123456789', { visibleRanges: [[0, 2], [8, 11]] });
|
|
165
|
+
// Output: '628*****6789'
|
|
166
|
+
|
|
167
|
+
// Show only middle section
|
|
168
|
+
maskPhone('1234567890', { visibleRanges: [[3, 6]] });
|
|
169
|
+
// Output: '***4567***'
|
|
170
|
+
|
|
171
|
+
// Multiple non-contiguous ranges
|
|
172
|
+
maskPhone('+441234567890', {
|
|
173
|
+
visibleRanges: [[0, 2], [6, 8]]
|
|
174
|
+
});
|
|
175
|
+
// Output: '+44***456****'
|
|
176
|
+
|
|
177
|
+
// Works with preserveFormat
|
|
178
|
+
maskPhone('+1 (555) 123-4567', {
|
|
179
|
+
visibleRanges: [[0, 3], [12, 15]],
|
|
180
|
+
preserveFormat: true
|
|
181
|
+
});
|
|
182
|
+
// Output: '+1 (555) ***-****'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Custom Masking Function
|
|
186
|
+
|
|
187
|
+
Full control over masking logic:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Mask every other character
|
|
191
|
+
maskPhone('1234567890', {
|
|
192
|
+
customMask: (char, idx) => idx % 2 === 0 ? '*' : char
|
|
193
|
+
});
|
|
194
|
+
// Output: '*2*4*6*8*0'
|
|
195
|
+
|
|
196
|
+
// Mask based on position
|
|
197
|
+
maskPhone('1234567890', {
|
|
198
|
+
customMask: (char, idx, phone) =>
|
|
199
|
+
idx < phone.length / 2 ? '*' : char
|
|
200
|
+
});
|
|
201
|
+
// Output: '*****67890'
|
|
202
|
+
|
|
203
|
+
// Conditional masking
|
|
204
|
+
maskPhone('1234567890', {
|
|
205
|
+
customMask: (char, idx) =>
|
|
206
|
+
['1', '3', '5', '7', '9'].includes(char) ? '*' : char
|
|
207
|
+
});
|
|
208
|
+
// Output: '*2*4*6*8*0'
|
|
209
|
+
|
|
210
|
+
// Works with preserveFormat
|
|
211
|
+
maskPhone('+1 (555) 123-4567', {
|
|
212
|
+
preserveFormat: true,
|
|
213
|
+
customMask: (char, idx) =>
|
|
214
|
+
char.match(/\d/) && idx % 2 === 0 ? 'X' : char
|
|
215
|
+
});
|
|
216
|
+
// Output: '+X (X5X) X2X-X5X7'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Combined Options
|
|
220
|
+
|
|
221
|
+
Mix and match options for custom behavior:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Common pattern: show country code + last 4
|
|
225
|
+
maskPhone('628123456789', {
|
|
226
|
+
maskChar: '•',
|
|
227
|
+
showFirst: 3,
|
|
228
|
+
showLast: 4
|
|
229
|
+
});
|
|
230
|
+
// Output: '628•••••6789'
|
|
231
|
+
|
|
232
|
+
// Preserve format with custom mask
|
|
233
|
+
maskPhone('+1 (555) 123-4567', {
|
|
234
|
+
maskChar: 'X',
|
|
235
|
+
preserveFormat: true
|
|
236
|
+
});
|
|
237
|
+
// Output: '+X (XXX) XXX-4567'
|
|
238
|
+
|
|
239
|
+
// Everything combined
|
|
240
|
+
maskPhone('628123456789', {
|
|
241
|
+
maskChar: '#',
|
|
242
|
+
showFirst: 3,
|
|
243
|
+
showLast: 4
|
|
244
|
+
});
|
|
245
|
+
// Output: '628#####6789'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## API Reference
|
|
249
|
+
|
|
250
|
+
### `maskPhone(input, options?)`
|
|
251
|
+
|
|
252
|
+
Masks a phone number according to the provided options.
|
|
253
|
+
|
|
254
|
+
#### Parameters
|
|
255
|
+
|
|
256
|
+
- **input** (`string | number`) - The phone number to mask
|
|
257
|
+
- **options** (`MaskOptions`, optional) - Configuration options
|
|
258
|
+
|
|
259
|
+
#### Options
|
|
260
|
+
|
|
261
|
+
| Option | Type | Default | Description |
|
|
262
|
+
|--------|------|---------|-------------|
|
|
263
|
+
| `maskChar` | `string` | `'*'` | Character used for masking |
|
|
264
|
+
| `showFirst` | `number` | `0` | Number of digits to show at the beginning |
|
|
265
|
+
| `showLast` | `number` | `4` | Number of digits to show at the end |
|
|
266
|
+
| `showStart` | `number` | - | Alias for `showFirst` (for clarity) |
|
|
267
|
+
| `showEnd` | `number` | - | Alias for `showLast` (for clarity) |
|
|
268
|
+
| `visibleRanges` | `Array<[number, number]>` | - | Specific ranges to keep visible `[[start, end], ...]` |
|
|
269
|
+
| `preserveFormat` | `boolean` | `false` | Maintain original spacing/formatting from input |
|
|
270
|
+
| `customMask` | `function` | - | Custom masking function `(char, index, phone) => string` |
|
|
271
|
+
|
|
272
|
+
#### Returns
|
|
273
|
+
|
|
274
|
+
- (`string`) - The masked phone number
|
|
275
|
+
|
|
276
|
+
#### TypeScript Types
|
|
277
|
+
|
|
278
|
+
All types and interfaces exported from the package:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
/**
|
|
282
|
+
* Phone masking options
|
|
283
|
+
* All options are optional and use flat structure for simplicity
|
|
284
|
+
*/
|
|
285
|
+
export interface MaskOptions {
|
|
286
|
+
/**
|
|
287
|
+
* Character used for masking
|
|
288
|
+
* @default '*'
|
|
289
|
+
* @example '#', 'X', '•'
|
|
290
|
+
*/
|
|
291
|
+
maskChar?: string;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Number of characters to show from the start
|
|
295
|
+
* @example 3 → '628*******'
|
|
296
|
+
*/
|
|
297
|
+
showFirst?: number;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Number of characters to show from the end (most common pattern)
|
|
301
|
+
* @default 4
|
|
302
|
+
* @example 4 → '******7890'
|
|
303
|
+
*/
|
|
304
|
+
showLast?: number;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Alias for showFirst (for clarity)
|
|
308
|
+
* @example 2 → '62********'
|
|
309
|
+
*/
|
|
310
|
+
showStart?: number;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Alias for showLast (for clarity)
|
|
314
|
+
* @example 4 → '******7890'
|
|
315
|
+
*/
|
|
316
|
+
showEnd?: number;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Specific ranges to keep visible
|
|
320
|
+
* Array of [startIndex, endIndex] (inclusive)
|
|
321
|
+
* @example [[0, 2], [8, 10]] → '628****789*'
|
|
322
|
+
*/
|
|
323
|
+
visibleRanges?: Array<[number, number]>;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Preserve formatting characters (spaces, dashes, parentheses, etc.)
|
|
327
|
+
* When true: '+1 (555) 123-4567' → '+* (***) ***-4567'
|
|
328
|
+
* When false: '+1 (555) 123-4567' → '**************' (strips then masks)
|
|
329
|
+
* @default false
|
|
330
|
+
*/
|
|
331
|
+
preserveFormat?: boolean;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Custom masking function for full control
|
|
335
|
+
* Overrides all other options
|
|
336
|
+
* @param char - Current character
|
|
337
|
+
* @param index - Position in the phone string
|
|
338
|
+
* @param phone - Full phone string
|
|
339
|
+
* @returns Masked character or original
|
|
340
|
+
* @example (char, idx) => idx % 2 === 0 ? '*' : char
|
|
341
|
+
*/
|
|
342
|
+
customMask?: (char: string, index: number, phone: string) => string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Input type for phone parameter
|
|
347
|
+
* Accepts both string and number for flexibility
|
|
348
|
+
*/
|
|
349
|
+
export type PhoneInput = string | number;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Default masking options
|
|
353
|
+
*/
|
|
354
|
+
export const DEFAULT_OPTIONS: Required<Omit<MaskOptions, 'showFirst' | 'showStart' | 'showEnd' | 'visibleRanges' | 'customMask'>> = {
|
|
355
|
+
maskChar: '*',
|
|
356
|
+
showLast: 4,
|
|
357
|
+
preserveFormat: false,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Type guard to check if value is a valid phone input
|
|
362
|
+
*/
|
|
363
|
+
export function isValidPhoneInput(value: unknown): value is PhoneInput;
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Package Exports
|
|
367
|
+
|
|
368
|
+
The package exports the following members:
|
|
369
|
+
|
|
370
|
+
| Export | Type | Description |
|
|
371
|
+
|--------|------|-------------|
|
|
372
|
+
| `maskPhone` | `function` | Main masking function |
|
|
373
|
+
| `MaskOptions` | `interface` | Configuration options interface (type-only) |
|
|
374
|
+
| `PhoneInput` | `type` | Union type: `string \| number` (type-only) |
|
|
375
|
+
| `DEFAULT_OPTIONS` | `const` | Default configuration constants |
|
|
376
|
+
| `isValidPhoneInput` | `function` | Type guard for runtime validation |
|
|
377
|
+
|
|
378
|
+
**Import examples:**
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// Import only what you need (tree-shakeable)
|
|
382
|
+
import { maskPhone } from '@ekaone/mask-phone';
|
|
383
|
+
|
|
384
|
+
// Import with types (TypeScript)
|
|
385
|
+
import {
|
|
386
|
+
maskPhone,
|
|
387
|
+
type MaskOptions,
|
|
388
|
+
type PhoneInput
|
|
389
|
+
} from '@ekaone/mask-phone';
|
|
390
|
+
|
|
391
|
+
// Import everything
|
|
392
|
+
import {
|
|
393
|
+
maskPhone,
|
|
394
|
+
type MaskOptions,
|
|
395
|
+
type PhoneInput,
|
|
396
|
+
DEFAULT_OPTIONS,
|
|
397
|
+
isValidPhoneInput
|
|
398
|
+
} from '@ekaone/mask-phone';
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Real-World Use Cases
|
|
402
|
+
|
|
403
|
+
### Customer Service Display
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
const phoneNumber = '+62 812 3456 7890';
|
|
407
|
+
const maskedPhone = maskPhone(phoneNumber, {
|
|
408
|
+
showFirst: 3,
|
|
409
|
+
preserveFormat: true
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
console.log(`Contact: ${maskedPhone}`);
|
|
413
|
+
// Output: "Contact: +62 *** **** 7890"
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### User Profile Display
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const userPhone = '1234567890';
|
|
420
|
+
const displayPhone = maskPhone(userPhone);
|
|
421
|
+
|
|
422
|
+
console.log(`Phone: ${displayPhone}`);
|
|
423
|
+
// Output: "Phone: ******7890"
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### SMS Verification Display
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
function showVerificationTarget(phone: string) {
|
|
430
|
+
return maskPhone(phone, {
|
|
431
|
+
showLast: 4,
|
|
432
|
+
preserveFormat: true
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const phone = '+1 (555) 123-4567';
|
|
437
|
+
console.log(`Code sent to: ${showVerificationTarget(phone)}`);
|
|
438
|
+
// Output: "Code sent to: +* (***) ***-4567"
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Security Levels
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
function maskPhoneBySecurityLevel(
|
|
445
|
+
phone: string,
|
|
446
|
+
level: 'low' | 'medium' | 'high'
|
|
447
|
+
) {
|
|
448
|
+
switch (level) {
|
|
449
|
+
case 'low':
|
|
450
|
+
return maskPhone(phone, { showFirst: 3, showLast: 4 });
|
|
451
|
+
case 'medium':
|
|
452
|
+
return maskPhone(phone, { showLast: 4 });
|
|
453
|
+
case 'high':
|
|
454
|
+
return maskPhone(phone, { showFirst: 0, showLast: 0 });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const phone = '628123456789';
|
|
459
|
+
console.log('Low: ', maskPhoneBySecurityLevel(phone, 'low'));
|
|
460
|
+
console.log('Medium:', maskPhoneBySecurityLevel(phone, 'medium'));
|
|
461
|
+
console.log('High: ', maskPhoneBySecurityLevel(phone, 'high'));
|
|
462
|
+
// Output:
|
|
463
|
+
// Low: 628*****6789
|
|
464
|
+
// Medium: ********6789
|
|
465
|
+
// High: ************
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Multi-User Contact List
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
const contacts = [
|
|
472
|
+
{ name: 'Alice', phone: '+1 (415) 555-2671' },
|
|
473
|
+
{ name: 'Bob', phone: '+44 20 7123 4567' },
|
|
474
|
+
{ name: 'Charlie', phone: '+62 812 3456 7890' }
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
contacts.forEach(contact => {
|
|
478
|
+
const masked = maskPhone(contact.phone, {
|
|
479
|
+
preserveFormat: true
|
|
480
|
+
});
|
|
481
|
+
console.log(`${contact.name}: ${masked}`);
|
|
482
|
+
});
|
|
483
|
+
// Output:
|
|
484
|
+
// Alice: +* (***) ***-2671
|
|
485
|
+
// Bob: +** ** **** 4567
|
|
486
|
+
// Charlie: +** *** **** 7890
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Audit Logging
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
function logPhoneAccess(phone: string, action: string) {
|
|
493
|
+
const maskedPhone = maskPhone(phone, {
|
|
494
|
+
showFirst: 2,
|
|
495
|
+
showLast: 0
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
console.log(`[${new Date().toISOString()}] ${action}: ${maskedPhone}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
logPhoneAccess('+1234567890', 'Phone number viewed');
|
|
502
|
+
// Output: "[2025-02-03T10:30:00.000Z] Phone number viewed: +1*********"
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Invoice/Receipt Display
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
const receiptPhone = '+62 812 3456 7890';
|
|
509
|
+
const formatted = maskPhone(receiptPhone, {
|
|
510
|
+
maskChar: '•',
|
|
511
|
+
preserveFormat: true
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
console.log(`Contact: ${formatted}`);
|
|
515
|
+
// Output: "Contact: +•• ••• •••• 7890"
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## International Phone Format Support
|
|
519
|
+
|
|
520
|
+
Works seamlessly with all international phone formats without any locale assumptions:
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
// United States
|
|
524
|
+
maskPhone('+1 (415) 555-2671', { preserveFormat: true });
|
|
525
|
+
// Output: '+* (***) ***-2671'
|
|
526
|
+
|
|
527
|
+
// United Kingdom
|
|
528
|
+
maskPhone('+44 20 7123 4567', { preserveFormat: true });
|
|
529
|
+
// Output: '+** ** **** 4567'
|
|
530
|
+
|
|
531
|
+
// Indonesia
|
|
532
|
+
maskPhone('+62 812 3456 7890', { preserveFormat: true });
|
|
533
|
+
// Output: '+** *** **** 7890'
|
|
534
|
+
|
|
535
|
+
// France
|
|
536
|
+
maskPhone('+33 1 23 45 67 89', { preserveFormat: true });
|
|
537
|
+
// Output: '+** * ** ** 67 89'
|
|
538
|
+
|
|
539
|
+
// Japan
|
|
540
|
+
maskPhone('+81 3-1234-5678', { preserveFormat: true });
|
|
541
|
+
// Output: '+** *-****-5678'
|
|
542
|
+
|
|
543
|
+
// Australia
|
|
544
|
+
maskPhone('+61 2 1234 5678', { preserveFormat: true });
|
|
545
|
+
// Output: '+** * **** 5678'
|
|
546
|
+
|
|
547
|
+
// Germany
|
|
548
|
+
maskPhone('+49 30 12345678', { preserveFormat: true });
|
|
549
|
+
// Output: '+** ** ****5678'
|
|
550
|
+
|
|
551
|
+
// Brazil
|
|
552
|
+
maskPhone('+55 11 91234-5678', { preserveFormat: true });
|
|
553
|
+
// Output: '+** ** *****-5678'
|
|
554
|
+
|
|
555
|
+
// China
|
|
556
|
+
maskPhone('+86 138 0013 8000', { preserveFormat: true });
|
|
557
|
+
// Output: '+** *** **** 8000'
|
|
558
|
+
|
|
559
|
+
// Russia
|
|
560
|
+
maskPhone('+7 495 123-45-67', { preserveFormat: true });
|
|
561
|
+
// Output: '+* *** ***-**-67'
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Security & Privacy
|
|
565
|
+
|
|
566
|
+
### GDPR Compliance
|
|
567
|
+
|
|
568
|
+
This library helps comply with GDPR (General Data Protection Regulation) requirements for handling personal data:
|
|
569
|
+
|
|
570
|
+
📋 **GDPR Article 32 - Security of Processing**
|
|
571
|
+
- Pseudonymization and masking of personal data
|
|
572
|
+
- Phone number masking is a technical measure for privacy protection
|
|
573
|
+
|
|
574
|
+
**Recommended practices:**
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
// ✅ GDPR Compliant (Last 4 only - Default)
|
|
578
|
+
maskPhone('+1234567890');
|
|
579
|
+
// Output: '+******7890'
|
|
580
|
+
|
|
581
|
+
// ✅ GDPR Compliant (Minimal disclosure)
|
|
582
|
+
maskPhone('+1234567890', { showLast: 2 });
|
|
583
|
+
// Output: '+*********90'
|
|
584
|
+
|
|
585
|
+
// ⚠️ Use with caution (more data disclosed)
|
|
586
|
+
maskPhone('+1234567890', { showFirst: 3, showLast: 4 });
|
|
587
|
+
// Output: '+12****7890'
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Privacy Best Practices
|
|
591
|
+
|
|
592
|
+
Following **OWASP** and **NIST SP 800-122** guidelines:
|
|
593
|
+
|
|
594
|
+
1. **Minimize data exposure** - Show only last 4 digits (default)
|
|
595
|
+
2. **Never log unmasked phone numbers** in production systems
|
|
596
|
+
3. **Use masking for display purposes** - Don't use for authentication
|
|
597
|
+
4. **This library is for display only** - Not for validation or storage
|
|
598
|
+
5. **Backend compliance** - Ensure server properly handles PII
|
|
599
|
+
|
|
600
|
+
### Important Notice
|
|
601
|
+
|
|
602
|
+
🔒 **This library is designed for display and logging purposes.** It does not:
|
|
603
|
+
- Store phone numbers securely
|
|
604
|
+
- Validate phone number format or authenticity
|
|
605
|
+
- Hash or encrypt phone data
|
|
606
|
+
- Handle actual telecommunications
|
|
607
|
+
- Provide country/carrier detection
|
|
608
|
+
|
|
609
|
+
This is a **pure masking utility** that works in both frontend and backend environments (Node.js, browser, serverless functions, etc.).
|
|
610
|
+
|
|
611
|
+
Always ensure your systems comply with GDPR, CCPA, and local data protection regulations when handling personal information.
|
|
612
|
+
|
|
613
|
+
## Edge Cases
|
|
614
|
+
|
|
615
|
+
The library handles various edge cases gracefully:
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// Very short numbers (won't mask if length ≤ showLast)
|
|
619
|
+
maskPhone('1234');
|
|
620
|
+
// Output: '1234'
|
|
621
|
+
|
|
622
|
+
maskPhone('12345');
|
|
623
|
+
// Output: '*2345'
|
|
624
|
+
|
|
625
|
+
// Empty input
|
|
626
|
+
maskPhone('');
|
|
627
|
+
// Output: ''
|
|
628
|
+
|
|
629
|
+
// Null/undefined
|
|
630
|
+
maskPhone(null);
|
|
631
|
+
// Output: ''
|
|
632
|
+
|
|
633
|
+
// Whitespace only
|
|
634
|
+
maskPhone(' ');
|
|
635
|
+
// Output: ''
|
|
636
|
+
|
|
637
|
+
// Mixed characters (auto-strips non-digits except +)
|
|
638
|
+
maskPhone('+1-ABC-234-5678');
|
|
639
|
+
// Output: '+******5678'
|
|
640
|
+
|
|
641
|
+
// Multiple + signs (keeps only digits and first +)
|
|
642
|
+
maskPhone('++1234567890');
|
|
643
|
+
// Output: '+******7890'
|
|
644
|
+
|
|
645
|
+
// Only + sign
|
|
646
|
+
maskPhone('+');
|
|
647
|
+
// Output: '+'
|
|
648
|
+
|
|
649
|
+
// Very long numbers
|
|
650
|
+
maskPhone('123456789012345678901234567890');
|
|
651
|
+
// Output: '**************************7890'
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
## Performance
|
|
655
|
+
|
|
656
|
+
- ⚡ Lightweight: < 2KB minified + gzipped
|
|
657
|
+
- 🚀 Zero dependencies
|
|
658
|
+
- 💨 Fast execution (< 1ms for typical phones)
|
|
659
|
+
- 🌳 Tree-shakeable with `sideEffects: false`
|
|
660
|
+
- 📦 Supports both CJS and ESM
|
|
661
|
+
|
|
662
|
+
## Browser Support
|
|
663
|
+
|
|
664
|
+
This library works in all modern browsers and Node.js environments that support ES2020+.
|
|
665
|
+
|
|
666
|
+
- ✅ Chrome (latest)
|
|
667
|
+
- ✅ Firefox (latest)
|
|
668
|
+
- ✅ Safari (latest)
|
|
669
|
+
- ✅ Edge (latest)
|
|
670
|
+
- ✅ Node.js 14+
|
|
671
|
+
- ✅ Deno
|
|
672
|
+
- ✅ Bun
|
|
673
|
+
|
|
674
|
+
## Importing Types
|
|
675
|
+
|
|
676
|
+
All types and utilities are exported for TypeScript users:
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
import {
|
|
680
|
+
maskPhone, // Main function
|
|
681
|
+
type MaskOptions, // Options interface
|
|
682
|
+
type PhoneInput, // Input type (string | number)
|
|
683
|
+
DEFAULT_OPTIONS, // Default configuration constants
|
|
684
|
+
isValidPhoneInput // Type guard utility
|
|
685
|
+
} from '@ekaone/mask-phone';
|
|
686
|
+
|
|
687
|
+
// Using the type guard
|
|
688
|
+
function processPhone(input: unknown) {
|
|
689
|
+
if (isValidPhoneInput(input)) {
|
|
690
|
+
return maskPhone(input);
|
|
691
|
+
}
|
|
692
|
+
throw new Error('Invalid phone input');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Using DEFAULT_OPTIONS
|
|
696
|
+
const customOptions: MaskOptions = {
|
|
697
|
+
...DEFAULT_OPTIONS,
|
|
698
|
+
showFirst: 3,
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// Strongly typed options
|
|
702
|
+
const options: MaskOptions = {
|
|
703
|
+
maskChar: '•',
|
|
704
|
+
showLast: 4,
|
|
705
|
+
preserveFormat: true,
|
|
706
|
+
visibleRanges: [[0, 2], [8, 10]],
|
|
707
|
+
customMask: (char, idx, phone) => char === '5' ? 'X' : char
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// Type-safe function wrapper
|
|
711
|
+
function safeMaskPhone(phone: PhoneInput, options?: MaskOptions): string {
|
|
712
|
+
return maskPhone(phone, options);
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
## TypeScript Support
|
|
717
|
+
|
|
718
|
+
Full TypeScript support with comprehensive type definitions included.
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
import {
|
|
722
|
+
maskPhone,
|
|
723
|
+
type MaskOptions,
|
|
724
|
+
type PhoneInput,
|
|
725
|
+
DEFAULT_OPTIONS,
|
|
726
|
+
isValidPhoneInput
|
|
727
|
+
} from '@ekaone/mask-phone';
|
|
728
|
+
|
|
729
|
+
// Basic usage with types
|
|
730
|
+
const phone: PhoneInput = '+1234567890';
|
|
731
|
+
const options: MaskOptions = {
|
|
732
|
+
maskChar: '•',
|
|
733
|
+
showLast: 4,
|
|
734
|
+
preserveFormat: true
|
|
735
|
+
};
|
|
736
|
+
const masked: string = maskPhone(phone, options);
|
|
737
|
+
|
|
738
|
+
// Using type guard for runtime validation
|
|
739
|
+
function handleUserInput(input: unknown): string {
|
|
740
|
+
if (!isValidPhoneInput(input)) {
|
|
741
|
+
throw new Error('Invalid phone number format');
|
|
742
|
+
}
|
|
743
|
+
return maskPhone(input);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Extending options with custom configuration
|
|
747
|
+
interface MyAppPhoneOptions extends MaskOptions {
|
|
748
|
+
logAccess?: boolean;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function maskWithLogging(
|
|
752
|
+
phone: PhoneInput,
|
|
753
|
+
options: MyAppPhoneOptions
|
|
754
|
+
): string {
|
|
755
|
+
const masked = maskPhone(phone, options);
|
|
756
|
+
if (options.logAccess) {
|
|
757
|
+
console.log('Phone accessed:', masked);
|
|
758
|
+
}
|
|
759
|
+
return masked;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Type-safe custom masking function
|
|
763
|
+
const customMask: MaskOptions['customMask'] = (
|
|
764
|
+
char: string,
|
|
765
|
+
index: number,
|
|
766
|
+
phone: string
|
|
767
|
+
): string => {
|
|
768
|
+
return index % 2 === 0 ? '*' : char;
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
maskPhone('1234567890', { customMask });
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### IntelliSense Support
|
|
775
|
+
|
|
776
|
+
The package provides full IntelliSense support in various IDEs and other TypeScript-aware editors:
|
|
777
|
+
|
|
778
|
+
- 💡 Auto-completion for all options
|
|
779
|
+
- 📝 Inline documentation with examples
|
|
780
|
+
- 🔍 Type checking for function parameters
|
|
781
|
+
- ⚠️ Compile-time error detection
|
|
782
|
+
|
|
783
|
+
## Why Choose mask-phone?
|
|
784
|
+
|
|
785
|
+
### ✅ Locale-Agnostic Design
|
|
786
|
+
Unlike other phone masking libraries, we make **zero assumptions** about phone number format. Works with any country, any format, any length.
|
|
787
|
+
|
|
788
|
+
### ✅ Format Preservation
|
|
789
|
+
Keep your original formatting with `preserveFormat: true` - perfect for UI display.
|
|
790
|
+
|
|
791
|
+
### ✅ Maximum Flexibility
|
|
792
|
+
From simple last-4 masking to complex custom functions - you're in control.
|
|
793
|
+
|
|
794
|
+
### ✅ Security First
|
|
795
|
+
Follows GDPR, OWASP, and NIST guidelines for PII protection.
|
|
796
|
+
|
|
797
|
+
### ✅ Developer Experience
|
|
798
|
+
- Full TypeScript support
|
|
799
|
+
- Intuitive API
|
|
800
|
+
- Comprehensive documentation
|
|
801
|
+
- Extensive test coverage
|
|
802
|
+
|
|
803
|
+
## Contributing
|
|
804
|
+
|
|
805
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
806
|
+
|
|
807
|
+
1. Fork the repository
|
|
808
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
809
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
810
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
811
|
+
5. Open a Pull Request
|
|
812
|
+
|
|
813
|
+
## Development
|
|
814
|
+
|
|
815
|
+
```bash
|
|
816
|
+
# Install dependencies
|
|
817
|
+
npm install
|
|
818
|
+
|
|
819
|
+
# Run tests
|
|
820
|
+
npm test
|
|
821
|
+
|
|
822
|
+
# Watch mode
|
|
823
|
+
npm run test:watch
|
|
824
|
+
|
|
825
|
+
# Coverage
|
|
826
|
+
npm run test:coverage
|
|
827
|
+
|
|
828
|
+
# Build
|
|
829
|
+
npm run build
|
|
830
|
+
|
|
831
|
+
# Type check
|
|
832
|
+
npm run type-check
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
## License
|
|
836
|
+
|
|
837
|
+
MIT © Eka Prasetia
|
|
838
|
+
|
|
839
|
+
## Links
|
|
840
|
+
|
|
841
|
+
- [npm Package](https://www.npmjs.com/package/@ekaone/mask-phone)
|
|
842
|
+
- [GitHub Repository](https://github.com/ekaone/mask-phone)
|
|
843
|
+
- [Issue Tracker](https://github.com/ekaone/mask-phone/issues)
|
|
844
|
+
|
|
845
|
+
## Related Packages
|
|
846
|
+
|
|
847
|
+
- [@ekaone/mask-card](https://www.npmjs.com/package/@ekaone/mask-card) - Credit card masking library
|
|
848
|
+
- [@ekaone/mask-email](https://www.npmjs.com/package/@ekaone/mask-email) - Email address masking library
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
⭐ If this library helps you, please consider giving it a star on GitHub!
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone masking options
|
|
3
|
+
* All options are optional and use flat structure for simplicity
|
|
4
|
+
*/
|
|
5
|
+
interface MaskOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Character used for masking
|
|
8
|
+
* @default '*'
|
|
9
|
+
* @example '#', 'X', '•'
|
|
10
|
+
*/
|
|
11
|
+
maskChar?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Number of characters to show from the start
|
|
14
|
+
* @example 3 → '628*******'
|
|
15
|
+
*/
|
|
16
|
+
showFirst?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Number of characters to show from the end (most common pattern)
|
|
19
|
+
* @default 4
|
|
20
|
+
* @example 4 → '******7890'
|
|
21
|
+
*/
|
|
22
|
+
showLast?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Alias for showFirst (for clarity)
|
|
25
|
+
* @example 2 → '62********'
|
|
26
|
+
*/
|
|
27
|
+
showStart?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Alias for showLast (for clarity)
|
|
30
|
+
* @example 4 → '******7890'
|
|
31
|
+
*/
|
|
32
|
+
showEnd?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Specific ranges to keep visible
|
|
35
|
+
* Array of [startIndex, endIndex] (inclusive)
|
|
36
|
+
* @example [[0, 2], [8, 10]] → '628****789*'
|
|
37
|
+
*/
|
|
38
|
+
visibleRanges?: Array<[number, number]>;
|
|
39
|
+
/**
|
|
40
|
+
* Preserve formatting characters (spaces, dashes, parentheses, etc.)
|
|
41
|
+
* When true: '+1 (555) 123-4567' → '+* (***) ***-4567'
|
|
42
|
+
* When false: '+1 (555) 123-4567' → '**************' (strips then masks)
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
preserveFormat?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Custom masking function for full control
|
|
48
|
+
* Overrides all other options
|
|
49
|
+
* @param char - Current character
|
|
50
|
+
* @param index - Position in the phone string
|
|
51
|
+
* @param phone - Full phone string
|
|
52
|
+
* @returns Masked character or original
|
|
53
|
+
* @example (char, idx) => idx % 2 === 0 ? '*' : char
|
|
54
|
+
*/
|
|
55
|
+
customMask?: (char: string, index: number, phone: string) => string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Input type for phone parameter
|
|
59
|
+
* Accepts both string and number for flexibility
|
|
60
|
+
*/
|
|
61
|
+
type PhoneInput = string | number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Main masking function
|
|
65
|
+
* Masks phone numbers with flexible options
|
|
66
|
+
*
|
|
67
|
+
* @param phone - Phone number as string or number
|
|
68
|
+
* @param options - Masking options
|
|
69
|
+
* @returns Masked phone string
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* maskPhone('+1234567890', { showLast: 4 })
|
|
74
|
+
* // Returns: '******7890'
|
|
75
|
+
*
|
|
76
|
+
* maskPhone('+1 (555) 123-4567', { showLast: 4, preserveFormat: true })
|
|
77
|
+
* // Returns: '+* (***) ***-4567'
|
|
78
|
+
*
|
|
79
|
+
* maskPhone('628123456789', { showFirst: 3, showLast: 2 })
|
|
80
|
+
* // Returns: '628*******89'
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare const maskPhone: (phone: PhoneInput, options?: MaskOptions) => string;
|
|
84
|
+
|
|
85
|
+
export { type MaskOptions, type PhoneInput, maskPhone };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phone masking options
|
|
3
|
+
* All options are optional and use flat structure for simplicity
|
|
4
|
+
*/
|
|
5
|
+
interface MaskOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Character used for masking
|
|
8
|
+
* @default '*'
|
|
9
|
+
* @example '#', 'X', '•'
|
|
10
|
+
*/
|
|
11
|
+
maskChar?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Number of characters to show from the start
|
|
14
|
+
* @example 3 → '628*******'
|
|
15
|
+
*/
|
|
16
|
+
showFirst?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Number of characters to show from the end (most common pattern)
|
|
19
|
+
* @default 4
|
|
20
|
+
* @example 4 → '******7890'
|
|
21
|
+
*/
|
|
22
|
+
showLast?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Alias for showFirst (for clarity)
|
|
25
|
+
* @example 2 → '62********'
|
|
26
|
+
*/
|
|
27
|
+
showStart?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Alias for showLast (for clarity)
|
|
30
|
+
* @example 4 → '******7890'
|
|
31
|
+
*/
|
|
32
|
+
showEnd?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Specific ranges to keep visible
|
|
35
|
+
* Array of [startIndex, endIndex] (inclusive)
|
|
36
|
+
* @example [[0, 2], [8, 10]] → '628****789*'
|
|
37
|
+
*/
|
|
38
|
+
visibleRanges?: Array<[number, number]>;
|
|
39
|
+
/**
|
|
40
|
+
* Preserve formatting characters (spaces, dashes, parentheses, etc.)
|
|
41
|
+
* When true: '+1 (555) 123-4567' → '+* (***) ***-4567'
|
|
42
|
+
* When false: '+1 (555) 123-4567' → '**************' (strips then masks)
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
preserveFormat?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Custom masking function for full control
|
|
48
|
+
* Overrides all other options
|
|
49
|
+
* @param char - Current character
|
|
50
|
+
* @param index - Position in the phone string
|
|
51
|
+
* @param phone - Full phone string
|
|
52
|
+
* @returns Masked character or original
|
|
53
|
+
* @example (char, idx) => idx % 2 === 0 ? '*' : char
|
|
54
|
+
*/
|
|
55
|
+
customMask?: (char: string, index: number, phone: string) => string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Input type for phone parameter
|
|
59
|
+
* Accepts both string and number for flexibility
|
|
60
|
+
*/
|
|
61
|
+
type PhoneInput = string | number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Main masking function
|
|
65
|
+
* Masks phone numbers with flexible options
|
|
66
|
+
*
|
|
67
|
+
* @param phone - Phone number as string or number
|
|
68
|
+
* @param options - Masking options
|
|
69
|
+
* @returns Masked phone string
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* maskPhone('+1234567890', { showLast: 4 })
|
|
74
|
+
* // Returns: '******7890'
|
|
75
|
+
*
|
|
76
|
+
* maskPhone('+1 (555) 123-4567', { showLast: 4, preserveFormat: true })
|
|
77
|
+
* // Returns: '+* (***) ***-4567'
|
|
78
|
+
*
|
|
79
|
+
* maskPhone('628123456789', { showFirst: 3, showLast: 2 })
|
|
80
|
+
* // Returns: '628*******89'
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare const maskPhone: (phone: PhoneInput, options?: MaskOptions) => string;
|
|
84
|
+
|
|
85
|
+
export { type MaskOptions, type PhoneInput, maskPhone };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var m=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var k=(r,t)=>{for(var s in t)m(r,s,{get:t[s],enumerable:!0})},p=(r,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of b(t))!c.call(r,e)&&e!==s&&m(r,e,{get:()=>t[e],enumerable:!(n=f(t,e))||n.enumerable});return r};var w=r=>p(m({},"__esModule",{value:!0}),r);var C={};k(C,{maskPhone:()=>L});module.exports=w(C);var i={maskChar:"*",showLast:4,preserveFormat:!1};function F(r){return r==null?"":(typeof r=="object"&&r!==null&&"phone"in r&&(r=r.phone),String(r).trim())}function l(r){return r.replace(/[^\d+]/g,"")}function O(r){var n,e,a,u,o,h;if(!r)return{maskChar:i.maskChar,showFirst:0,showLast:i.showLast,preserveFormat:i.preserveFormat};let t=(e=(n=r.showFirst)!=null?n:r.showStart)!=null?e:0,s=(u=(a=r.showLast)!=null?a:r.showEnd)!=null?u:i.showLast;return{maskChar:(o=r.maskChar)!=null?o:i.maskChar,showFirst:t,showLast:s,visibleRanges:r.visibleRanges,preserveFormat:(h=r.preserveFormat)!=null?h:i.preserveFormat,customMask:r.customMask}}function g(r,t,s){return s.visibleRanges&&s.visibleRanges.length>0?s.visibleRanges.some(([n,e])=>r>=n&&r<=e):s.showFirst>0&&r<s.showFirst||s.showLast>0&&r>=t-s.showLast}function v(r){return/[\s\-().\+]/.test(r)}function d(r,t){let s=-1,e=r.replace(/\D/g,"").length;return r.split("").map((a,u)=>t.customMask?t.customMask(a,u,r):v(a)||(s++,g(s,e,t))?a:t.maskChar).join("")}function M(r,t){let s=l(r),n=s.length;return s.split("").map((e,a)=>t.customMask?t.customMask(e,a,s):e==="+"||g(a,n,t)?e:t.maskChar).join("")}var L=(r,t)=>{let s=F(r);if(!s)return"";let n=O(t),e=l(s),a=n.showFirst+n.showLast;return!(t!=null&&t.customMask)&&!(t!=null&&t.visibleRanges)&&e.length<=a?s:n.preserveFormat?d(s,n):M(s,n)};0&&(module.exports={maskPhone});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var i={maskChar:"*",showLast:4,preserveFormat:!1};function g(r){return r==null?"":(typeof r=="object"&&r!==null&&"phone"in r&&(r=r.phone),String(r).trim())}function h(r){return r.replace(/[^\d+]/g,"")}function f(r){var e,n,a,u,m,o;if(!r)return{maskChar:i.maskChar,showFirst:0,showLast:i.showLast,preserveFormat:i.preserveFormat};let t=(n=(e=r.showFirst)!=null?e:r.showStart)!=null?n:0,s=(u=(a=r.showLast)!=null?a:r.showEnd)!=null?u:i.showLast;return{maskChar:(m=r.maskChar)!=null?m:i.maskChar,showFirst:t,showLast:s,visibleRanges:r.visibleRanges,preserveFormat:(o=r.preserveFormat)!=null?o:i.preserveFormat,customMask:r.customMask}}function l(r,t,s){return s.visibleRanges&&s.visibleRanges.length>0?s.visibleRanges.some(([e,n])=>r>=e&&r<=n):s.showFirst>0&&r<s.showFirst||s.showLast>0&&r>=t-s.showLast}function b(r){return/[\s\-().\+]/.test(r)}function c(r,t){let s=-1,n=r.replace(/\D/g,"").length;return r.split("").map((a,u)=>t.customMask?t.customMask(a,u,r):b(a)||(s++,l(s,n,t))?a:t.maskChar).join("")}function k(r,t){let s=h(r),e=s.length;return s.split("").map((n,a)=>t.customMask?t.customMask(n,a,s):n==="+"||l(a,e,t)?n:t.maskChar).join("")}var F=(r,t)=>{let s=g(r);if(!s)return"";let e=f(t),n=h(s),a=e.showFirst+e.showLast;return!(t!=null&&t.customMask)&&!(t!=null&&t.visibleRanges)&&n.length<=a?s:e.preserveFormat?c(s,e):k(s,e)};export{F as maskPhone};
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ekaone/mask-phone",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight, zero-dependency TypeScript library for masking phone numbers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"phone",
|
|
7
|
+
"mask",
|
|
8
|
+
"obfuscate",
|
|
9
|
+
"security",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "Eka Prasetia",
|
|
14
|
+
"email": "ekaone3033@gmail.com",
|
|
15
|
+
"url": "https://prasetia.me"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"module": "./dist/index.mjs",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.mjs",
|
|
26
|
+
"require": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"dev": "tsup --watch",
|
|
40
|
+
"clean": "rimraf dist",
|
|
41
|
+
"prepublishOnly": "npm run clean && npm run build && npm test",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"test:ui": "vitest --ui",
|
|
45
|
+
"test:coverage": "vitest run --coverage"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/ekaone/mask-phone"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/ekaone/mask-phone/issues"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/ekaone/mask-phone#readme",
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^25.1.0",
|
|
57
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
58
|
+
"@vitest/ui": "^4.0.18",
|
|
59
|
+
"rimraf": "^6.0.0",
|
|
60
|
+
"tsup": "^8.5.1",
|
|
61
|
+
"typescript": "^5.7.0",
|
|
62
|
+
"vitest": "^4.0.18"
|
|
63
|
+
}
|
|
64
|
+
}
|