sms-verification-api 0.9.1
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/.env.example +20 -0
- package/DEPLOYMENT.md +151 -0
- package/README.md +475 -0
- package/docs/app/(home)/layout.tsx +7 -0
- package/docs/app/(home)/page.tsx +38 -0
- package/docs/app/docs/[[...slug]]/page.tsx +59 -0
- package/docs/app/docs/layout.tsx +12 -0
- package/docs/app/docs-og/[...slug]/route.ts +24 -0
- package/docs/app/globals.css +587 -0
- package/docs/app/layout.config.tsx +13 -0
- package/docs/app/layout.tsx +27 -0
- package/docs/app/logo.tsx +35 -0
- package/docs/content/docs/API_AUTHENTICATION.md +91 -0
- package/docs/content/docs/DEPLOYMENT.md +181 -0
- package/docs/content/docs/api/post.mdx +35 -0
- package/docs/content/docs/api/verify.mdx +34 -0
- package/docs/content/docs/meta.json +8 -0
- package/docs/content/docs/verify-legal-name.md +339 -0
- package/docs/lib/source.ts +14 -0
- package/docs/mdx-components.tsx +12 -0
- package/docs/next.config.mjs +51 -0
- package/docs/openapi.json +329 -0
- package/docs/package.json +37 -0
- package/docs/postcss.config.mjs +5 -0
- package/docs/scripts/generate-docs.mjs +23 -0
- package/docs/source.config.ts +5 -0
- package/docs/tsconfig.json +29 -0
- package/docs/worker.js +35 -0
- package/docs/wrangler.toml +26 -0
- package/examples/client.js +119 -0
- package/examples/demo.html +325 -0
- package/examples/libphonenumber-example.js +120 -0
- package/openapi.json +329 -0
- package/package.json +71 -0
- package/scripts/deploy.sh +63 -0
- package/src/identity-verification-server.ts +553 -0
- package/src/index.js +8 -0
- package/src/sns.js +236 -0
- package/src/verify-phone-server.js +448 -0
- package/src/verify-phone.ts +551 -0
- package/test/api.test.js +201 -0
- package/test/integration.test.js +152 -0
- package/test/metadata-test.js +73 -0
- package/test/server.test.js +143 -0
- package/test/setup.js +32 -0
- package/test/utils.test.js +186 -0
- package/test/verify.test.js +23 -0
- package/test/voip.test.js +112 -0
- package/vitest.config.js +10 -0
- package/wrangler.toml +27 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RootProvider } from 'fumadocs-ui/provider';
|
|
2
|
+
import './globals.css';
|
|
3
|
+
import { Inter, JetBrains_Mono } from 'next/font/google';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
const inter = Inter({
|
|
7
|
+
subsets: ['latin'],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const mono = JetBrains_Mono({
|
|
11
|
+
subsets: ['latin'],
|
|
12
|
+
variable: '--font-mono',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default function Layout({ children }: { children: ReactNode }) {
|
|
16
|
+
return (
|
|
17
|
+
<html
|
|
18
|
+
lang="en"
|
|
19
|
+
className={`${inter.className} ${mono.variable}`}
|
|
20
|
+
suppressHydrationWarning
|
|
21
|
+
>
|
|
22
|
+
<body className="flex flex-col min-h-screen">
|
|
23
|
+
<RootProvider>{children}</RootProvider>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
|
|
4
|
+
export function Logo() {
|
|
5
|
+
const id = useId();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
className="min-w-[50px] ms-2"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
width="93"
|
|
12
|
+
height="40"
|
|
13
|
+
viewBox="0 0 93 40"
|
|
14
|
+
id={id}
|
|
15
|
+
>
|
|
16
|
+
<path
|
|
17
|
+
d="M10.8 30.3C4.8 30.3 1.38 27.12 1.38 21.66V9.9H4.59V21.45C4.59 25.5 6.39 27.18 10.8 27.18C15.21 27.18 17.01 25.5 17.01 21.45V9.9H20.25V21.66C20.25 27.12 16.83 30.3 10.8 30.3ZM26.3611 30H23.1211V15.09H26.0911V19.71H26.3011C26.7511 17.19 28.7311 14.79 32.5111 14.79C36.6511 14.79 38.6911 17.58 38.6911 21.03V30H35.4511V21.9C35.4511 19.11 34.1911 17.7 31.1011 17.7C27.8311 17.7 26.3611 19.38 26.3611 22.62V30ZM44.8181 30H41.5781V9.9H44.8181V21H49.0781L53.5481 15.09H57.3281L51.7181 22.26L57.2981 30H53.4881L49.0781 23.91H44.8181V30ZM66.4219 30.3C61.5319 30.3 58.3219 27.54 58.3219 22.56C58.3219 17.91 61.5019 14.79 66.3619 14.79C70.9819 14.79 74.1319 17.34 74.1319 21.87C74.1319 22.41 74.1019 22.83 74.0119 23.28H61.3519C61.4719 26.16 62.8819 27.69 66.3319 27.69C69.4519 27.69 70.7419 26.67 70.7419 24.9V24.66H73.9819V24.93C73.9819 28.11 70.8619 30.3 66.4219 30.3ZM66.3019 17.34C63.0019 17.34 61.5619 18.81 61.3819 21.48H71.0719V21.42C71.0719 18.66 69.4819 17.34 66.3019 17.34ZM78.9586 35.1H76.8286V32.16H79.7386C81.0586 32.16 81.5986 31.8 82.0486 30.78L82.4086 30L75.0586 15.09H78.6886L82.4986 23.01L83.9686 26.58H84.2086L85.6186 22.98L89.1286 15.09H92.6986L84.9286 31.62C83.6986 34.29 82.0186 35.1 78.9586 35.1Z"
|
|
18
|
+
fill={`url(#${id}_radial_301_76)`}
|
|
19
|
+
/>
|
|
20
|
+
<defs>
|
|
21
|
+
<radialGradient
|
|
22
|
+
id={`${id}_radial_301_76`}
|
|
23
|
+
cx="0"
|
|
24
|
+
cy="0"
|
|
25
|
+
r="1"
|
|
26
|
+
gradientUnits="userSpaceOnUse"
|
|
27
|
+
gradientTransform="rotate(23.2729) scale(101.237 101.088)"
|
|
28
|
+
>
|
|
29
|
+
<stop offset="0.26875" stopColor="currentColor" />
|
|
30
|
+
<stop offset="0.904454" stopColor="currentColor" stopOpacity="0.5" />
|
|
31
|
+
</radialGradient>
|
|
32
|
+
</defs>
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API Authentication
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
This SMS Verification API requires authentication using an API key for all endpoints except:
|
|
6
|
+
- `/health` - Health check endpoint
|
|
7
|
+
- `/` - Swagger UI documentation (root path)
|
|
8
|
+
- `/openapi.json` - OpenAPI specification
|
|
9
|
+
|
|
10
|
+
## API Key
|
|
11
|
+
|
|
12
|
+
Your API key is: `sk_test_1234567890abcdef1234567890abcdef`
|
|
13
|
+
|
|
14
|
+
## Authentication Methods
|
|
15
|
+
|
|
16
|
+
You can provide your API key in one of two ways:
|
|
17
|
+
|
|
18
|
+
### Method 1: X-API-Key Header (Recommended)
|
|
19
|
+
```bash
|
|
20
|
+
curl -X POST https://your-worker.workers.dev/api/send-verification \
|
|
21
|
+
-H "Content-Type: application/json" \
|
|
22
|
+
-H "X-API-Key: sk_test_1234567890abcdef1234567890abcdef" \
|
|
23
|
+
-d '{"phoneNumber": "+1234567890"}'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Method 2: Authorization Header (Bearer Token)
|
|
27
|
+
```bash
|
|
28
|
+
curl -X POST https://your-worker.workers.dev/api/send-verification \
|
|
29
|
+
-H "Content-Type: application/json" \
|
|
30
|
+
-H "Authorization: Bearer sk_test_1234567890abcdef1234567890abcdef" \
|
|
31
|
+
-d '{"phoneNumber": "+1234567890"}'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## JavaScript/Node.js Examples
|
|
35
|
+
|
|
36
|
+
### Using X-API-Key Header
|
|
37
|
+
```javascript
|
|
38
|
+
const response = await fetch('https://your-worker.workers.dev/api/send-verification', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-API-Key': 'sk_test_1234567890abcdef1234567890abcdef'
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
phoneNumber: '+1234567890'
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Using Authorization Header
|
|
51
|
+
```javascript
|
|
52
|
+
const response = await fetch('https://your-worker.workers.dev/api/send-verification', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
'Authorization': 'Bearer sk_test_1234567890abcdef1234567890abcdef'
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
phoneNumber: '+1234567890'
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Error Responses
|
|
65
|
+
|
|
66
|
+
### Missing API Key (401 Unauthorized)
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"error": "API key required",
|
|
70
|
+
"message": "Please provide your API key in the Authorization header (Bearer token) or X-API-Key header"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Invalid API Key (401 Unauthorized)
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"error": "Invalid API key",
|
|
78
|
+
"message": "The provided API key is invalid"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Security Notes
|
|
83
|
+
|
|
84
|
+
- Keep your API key secure and do not share it publicly
|
|
85
|
+
- The API key is stored as a secret in Cloudflare Workers
|
|
86
|
+
- All API requests should be made over HTTPS
|
|
87
|
+
- Consider rotating your API key periodically for enhanced security
|
|
88
|
+
|
|
89
|
+
## Testing
|
|
90
|
+
|
|
91
|
+
You can test the API using the Swagger UI at the root path `/`, which will prompt you to enter your API key in the "Authorize" section.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cloudflare Workers Deployment Guide
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
This guide will help you deploy the SMS Verification API to Cloudflare Workers using Wrangler.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
1. **Cloudflare Account**: Sign up at [cloudflare.com](https://cloudflare.com)
|
|
10
|
+
2. **Wrangler CLI**: Install Wrangler globally
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g wrangler
|
|
13
|
+
```
|
|
14
|
+
3. **AWS Account**: You'll need AWS credentials for SNS SMS sending
|
|
15
|
+
|
|
16
|
+
## Setup Steps
|
|
17
|
+
|
|
18
|
+
### 1. Install Dependencies
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Login to Cloudflare
|
|
24
|
+
```bash
|
|
25
|
+
wrangler login
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 3. Configure AWS Credentials
|
|
29
|
+
Set your AWS credentials as Cloudflare Workers secrets:
|
|
30
|
+
```bash
|
|
31
|
+
wrangler secret put AWS_ACCESS_KEY_ID
|
|
32
|
+
# Enter your AWS Access Key ID when prompted
|
|
33
|
+
|
|
34
|
+
wrangler secret put AWS_SECRET_ACCESS_KEY
|
|
35
|
+
# Enter your AWS Secret Access Key when prompted
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 4. Update Configuration
|
|
39
|
+
Edit `wrangler.toml` to match your Cloudflare account:
|
|
40
|
+
```toml
|
|
41
|
+
name = "your-sms-verification-api"
|
|
42
|
+
main = "src/index.js"
|
|
43
|
+
compatibility_date = "2024-01-01"
|
|
44
|
+
|
|
45
|
+
[env.production]
|
|
46
|
+
name = "your-sms-verification-api"
|
|
47
|
+
|
|
48
|
+
[env.staging]
|
|
49
|
+
name = "your-sms-verification-api-staging"
|
|
50
|
+
|
|
51
|
+
[vars]
|
|
52
|
+
AWS_REGION = "us-east-1"
|
|
53
|
+
SMS_SENDER_ID = "YourApp"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 5. Update Server URLs
|
|
57
|
+
In `src/index.js`, update the server URLs in the OpenAPI configuration:
|
|
58
|
+
```javascript
|
|
59
|
+
servers: [
|
|
60
|
+
{
|
|
61
|
+
url: 'https://your-sms-verification-api.your-subdomain.workers.dev',
|
|
62
|
+
description: 'Production server',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
url: 'https://your-sms-verification-api-staging.your-subdomain.workers.dev',
|
|
66
|
+
description: 'Staging server',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
### Local Development
|
|
74
|
+
```bash
|
|
75
|
+
npm run dev
|
|
76
|
+
```
|
|
77
|
+
This starts a local development server at `http://localhost:8787`
|
|
78
|
+
|
|
79
|
+
### Testing
|
|
80
|
+
```bash
|
|
81
|
+
npm run test
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Deployment
|
|
85
|
+
|
|
86
|
+
### Deploy to Staging
|
|
87
|
+
```bash
|
|
88
|
+
npm run deploy:staging
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Deploy to Production
|
|
92
|
+
```bash
|
|
93
|
+
npm run deploy:production
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Deploy to Default Environment
|
|
97
|
+
```bash
|
|
98
|
+
npm run deploy
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Environment Variables
|
|
102
|
+
|
|
103
|
+
### Required Secrets (set with `wrangler secret put`)
|
|
104
|
+
- `AWS_ACCESS_KEY_ID`: Your AWS Access Key ID
|
|
105
|
+
- `AWS_SECRET_ACCESS_KEY`: Your AWS Secret Access Key
|
|
106
|
+
|
|
107
|
+
### Optional Variables (set in wrangler.toml)
|
|
108
|
+
- `AWS_REGION`: AWS region (default: us-east-1)
|
|
109
|
+
- `SMS_SENDER_ID`: Sender ID for SMS messages (default: Verify)
|
|
110
|
+
|
|
111
|
+
## Production Considerations
|
|
112
|
+
|
|
113
|
+
### 1. Use Cloudflare KV for Storage
|
|
114
|
+
Replace in-memory storage with Cloudflare KV for production:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// Add KV binding to wrangler.toml
|
|
118
|
+
[[kv_namespaces]]
|
|
119
|
+
binding = "VERIFICATION_CODES"
|
|
120
|
+
id = "your-kv-namespace-id"
|
|
121
|
+
preview_id = "your-preview-kv-namespace-id"
|
|
122
|
+
|
|
123
|
+
// Update storage in src/index.js
|
|
124
|
+
const verificationCodes = env.VERIFICATION_CODES;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Custom Domain
|
|
128
|
+
Add a custom domain in the Cloudflare dashboard and update your `wrangler.toml`:
|
|
129
|
+
```toml
|
|
130
|
+
[env.production]
|
|
131
|
+
name = "your-sms-verification-api"
|
|
132
|
+
routes = [
|
|
133
|
+
{ pattern = "api.yourdomain.com", zone_name = "yourdomain.com" }
|
|
134
|
+
]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Rate Limiting
|
|
138
|
+
Consider using Cloudflare's built-in rate limiting or implement more sophisticated rate limiting with KV storage.
|
|
139
|
+
|
|
140
|
+
## Monitoring
|
|
141
|
+
|
|
142
|
+
### Logs
|
|
143
|
+
View logs in the Cloudflare dashboard or using Wrangler:
|
|
144
|
+
```bash
|
|
145
|
+
wrangler tail
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Analytics
|
|
149
|
+
Monitor your API usage in the Cloudflare dashboard under Workers & Pages.
|
|
150
|
+
|
|
151
|
+
## Troubleshooting
|
|
152
|
+
|
|
153
|
+
### Common Issues
|
|
154
|
+
|
|
155
|
+
1. **CORS Errors**: The API is configured to allow all origins (`*`). Adjust in `src/index.js` if needed.
|
|
156
|
+
|
|
157
|
+
2. **AWS Credentials**: Ensure your AWS credentials have SNS SMS sending permissions.
|
|
158
|
+
|
|
159
|
+
3. **Memory Limits**: Cloudflare Workers have memory limits. The current implementation uses in-memory storage which resets on each request.
|
|
160
|
+
|
|
161
|
+
4. **Timeout Issues**: Workers have a 30-second timeout. Ensure your SNS requests complete within this time.
|
|
162
|
+
|
|
163
|
+
### Debug Mode
|
|
164
|
+
Enable debug logging by setting the log level in your Worker:
|
|
165
|
+
```javascript
|
|
166
|
+
app.use('*', logger({ level: 'debug' }));
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Security
|
|
170
|
+
|
|
171
|
+
- AWS credentials are stored as encrypted secrets
|
|
172
|
+
- CORS is configured for cross-origin requests
|
|
173
|
+
- Rate limiting is implemented to prevent abuse
|
|
174
|
+
- Input validation is performed on all endpoints
|
|
175
|
+
|
|
176
|
+
## Cost Optimization
|
|
177
|
+
|
|
178
|
+
- Cloudflare Workers are pay-per-request
|
|
179
|
+
- Monitor your usage in the Cloudflare dashboard
|
|
180
|
+
- Consider implementing caching for frequently accessed data
|
|
181
|
+
- Use KV storage efficiently to minimize read/write operations
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Send SMS verification code
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
A comprehensive SMS verification API using AWS SNS HTTP API with Hono server on Cloudflare Workers. The API is fully documented and driven by an OpenAPI 3.0 spec, available at:
|
|
10
|
+
## Features
|
|
11
|
+
- **Cloudflare Workers**: Serverless deployment with global edge network
|
|
12
|
+
- **Modern OpenAPI-driven contract**: Fully documented API with Swagger UI
|
|
13
|
+
- **SMS verification with optional custom code**: Send random or custom verification codes
|
|
14
|
+
- **VoIP number blocking**: Optional protection against VoIP numbers using phone lookup API
|
|
15
|
+
- **Code verification endpoint**: Verify submitted codes with attempt tracking
|
|
16
|
+
- **Health check endpoint**: Monitor API status
|
|
17
|
+
- **API Key Authentication**: Secure access control for all endpoints
|
|
18
|
+
- **Strong input validation**: E.164 phone numbers, 6-digit codes
|
|
19
|
+
- **Rate limiting and error handling**: Built-in protection against abuse
|
|
20
|
+
- **More info like names, emails**: See [Trestle](https://trestle-api.redoc.ly/Current/#section/Overview)
|
|
21
|
+
- **MIT licensed**: Open source and free to use
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#### Send Verification Code
|
|
26
|
+
`GET /api/send-verification`
|
|
27
|
+
- Sends a 6-digit verification code to the specified phone number via SMS.
|
|
28
|
+
- Optionally accepts a custom code (for testing or admin use).
|
|
29
|
+
- Optionally blocks VoIP numbers using phone lookup API.
|
|
30
|
+
|
|
31
|
+
**Query Parameters:**
|
|
32
|
+
- `phoneNumber` (required): Phone number in E.164 format (e.g., +1234567890)
|
|
33
|
+
- `code` (optional): Custom 6-digit verification code
|
|
34
|
+
- `blockVoip` (optional): Boolean flag to block VoIP numbers
|
|
35
|
+
<APIPage document={"openapi.json"} operations={[{"path":"/api/send-verification","method":"post"}]} webhooks={[]} hasHead={false} />
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Verify SMS code
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
#### Verify Code
|
|
6
|
+
`GET /api/verify-code`
|
|
7
|
+
- Verifies the submitted code against the sent verification code.
|
|
8
|
+
|
|
9
|
+
**Query Parameters:**
|
|
10
|
+
- `phoneNumber` (required): Phone number in E.164 format
|
|
11
|
+
- `code` (required): 6-digit verification code
|
|
12
|
+
|
|
13
|
+
**Example Request:**
|
|
14
|
+
```
|
|
15
|
+
GET /api/verify-code?phoneNumber=%2B1234567890&code=654321
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Response (success):**
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"success": true,
|
|
22
|
+
"message": "Phone number verified successfully",
|
|
23
|
+
"verificationToken": "unique-verification-token",
|
|
24
|
+
"phoneNumber": "+1234567890"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Response (error):**
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"error": "Invalid verification code",
|
|
32
|
+
"attemptsRemaining": 3
|
|
33
|
+
}
|
|
34
|
+
```
|