@narcisbodea/smstunnel-sdk 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/README.md +490 -0
- package/dist/index.d.mts +241 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +247 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +218 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +93 -0
- package/dist/react/index.d.ts +93 -0
- package/dist/react/index.js +665 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +624 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/server/index.d.mts +262 -0
- package/dist/server/index.d.ts +262 -0
- package/dist/server/index.js +493 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +471 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/vue/index.d.mts +155 -0
- package/dist/vue/index.d.ts +155 -0
- package/dist/vue/index.js +525 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/vue/index.mjs +484 -0
- package/dist/vue/index.mjs.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# smstunnel-sdk
|
|
2
|
+
|
|
3
|
+
SMSTunnel SDK - NestJS + React + Vue + framework-agnostic client for SMS pairing.
|
|
4
|
+
|
|
5
|
+
| Import path | Scope |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `smstunnel-sdk` | `SmsTunnelClient` class + types + labels (Angular, Svelte, Astro, Solid, Vanilla JS) |
|
|
8
|
+
| `smstunnel-sdk/server` | NestJS module (backend) |
|
|
9
|
+
| `smstunnel-sdk/react` | React components + hook (also Next.js, Remix, Gatsby) |
|
|
10
|
+
| `smstunnel-sdk/vue` | Vue 3 components + composable (also Nuxt) |
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install smstunnel-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Server (NestJS)
|
|
21
|
+
|
|
22
|
+
### 1. Implement a Storage Adapter
|
|
23
|
+
|
|
24
|
+
The SDK is database-agnostic. You provide a simple adapter with 3 methods:
|
|
25
|
+
|
|
26
|
+
#### MongoDB (Mongoose)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { SmsTunnelStorageAdapter, SmsTunnelConfig } from 'smstunnel-sdk/server';
|
|
30
|
+
import { Model } from 'mongoose';
|
|
31
|
+
|
|
32
|
+
export class MongoSmsTunnelStorage implements SmsTunnelStorageAdapter {
|
|
33
|
+
constructor(private model: Model<any>) {}
|
|
34
|
+
|
|
35
|
+
async getConfig(): Promise<Partial<SmsTunnelConfig>> {
|
|
36
|
+
const doc = await this.model.findOne().lean().exec();
|
|
37
|
+
return {
|
|
38
|
+
serverUrl: doc?.smstunnelServerUrl || '',
|
|
39
|
+
apiKey: doc?.smstunnelApiKey || '',
|
|
40
|
+
siteToken: doc?.smstunnelSiteToken || '',
|
|
41
|
+
deviceName: doc?.smstunnelDeviceName || '',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async updateConfig(partial: Partial<SmsTunnelConfig>): Promise<void> {
|
|
46
|
+
const $set: Record<string, string> = {};
|
|
47
|
+
if (partial.serverUrl !== undefined) $set.smstunnelServerUrl = partial.serverUrl;
|
|
48
|
+
if (partial.apiKey !== undefined) $set.smstunnelApiKey = partial.apiKey;
|
|
49
|
+
if (partial.siteToken !== undefined) $set.smstunnelSiteToken = partial.siteToken;
|
|
50
|
+
if (partial.deviceName !== undefined) $set.smstunnelDeviceName = partial.deviceName;
|
|
51
|
+
await this.model.updateOne({}, { $set });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async clearConfig(): Promise<void> {
|
|
55
|
+
await this.model.updateOne({}, {
|
|
56
|
+
$set: { smstunnelApiKey: '', smstunnelDeviceName: '', smstunnelSiteToken: '' },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Prisma (PostgreSQL)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { SmsTunnelStorageAdapter, SmsTunnelConfig } from 'smstunnel-sdk/server';
|
|
66
|
+
import { PrismaClient } from '@prisma/client';
|
|
67
|
+
|
|
68
|
+
export class PrismaSmsTunnelStorage implements SmsTunnelStorageAdapter {
|
|
69
|
+
constructor(private prisma: PrismaClient) {}
|
|
70
|
+
|
|
71
|
+
async getConfig(): Promise<Partial<SmsTunnelConfig>> {
|
|
72
|
+
const row = await this.prisma.appSettings.findFirst();
|
|
73
|
+
return {
|
|
74
|
+
serverUrl: row?.smstunnelServerUrl || '',
|
|
75
|
+
apiKey: row?.smstunnelApiKey || '',
|
|
76
|
+
siteToken: row?.smstunnelSiteToken || '',
|
|
77
|
+
deviceName: row?.smstunnelDeviceName || '',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async updateConfig(partial: Partial<SmsTunnelConfig>): Promise<void> {
|
|
82
|
+
await this.prisma.appSettings.updateMany({ data: partial });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async clearConfig(): Promise<void> {
|
|
86
|
+
await this.prisma.appSettings.updateMany({
|
|
87
|
+
data: { apiKey: '', deviceName: '', siteToken: '' },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### JSON File
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { SmsTunnelStorageAdapter, SmsTunnelConfig } from 'smstunnel-sdk/server';
|
|
97
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
98
|
+
|
|
99
|
+
export class JsonFileStorage implements SmsTunnelStorageAdapter {
|
|
100
|
+
constructor(private filePath: string) {}
|
|
101
|
+
|
|
102
|
+
async getConfig(): Promise<Partial<SmsTunnelConfig>> {
|
|
103
|
+
try { return JSON.parse(await readFile(this.filePath, 'utf-8')); }
|
|
104
|
+
catch { return {}; }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async updateConfig(partial: Partial<SmsTunnelConfig>): Promise<void> {
|
|
108
|
+
const config = { ...(await this.getConfig()), ...partial };
|
|
109
|
+
await writeFile(this.filePath, JSON.stringify(config, null, 2));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async clearConfig(): Promise<void> {
|
|
113
|
+
await this.updateConfig({ apiKey: '', deviceName: '', siteToken: '' });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. Register the Module
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { SmsTunnelModule } from 'smstunnel-sdk/server';
|
|
122
|
+
|
|
123
|
+
@Module({
|
|
124
|
+
imports: [
|
|
125
|
+
SmsTunnelModule.forRoot({
|
|
126
|
+
storage: new MongoSmsTunnelStorage(settingsModel),
|
|
127
|
+
callbackBaseUrl: 'https://myapp.com/api',
|
|
128
|
+
displayName: 'My App',
|
|
129
|
+
}),
|
|
130
|
+
],
|
|
131
|
+
})
|
|
132
|
+
export class AppModule {}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Or async:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
SmsTunnelModule.forRootAsync({
|
|
139
|
+
useFactory: (settingsModel) => ({
|
|
140
|
+
storage: new MongoSmsTunnelStorage(settingsModel),
|
|
141
|
+
callbackBaseUrl: process.env.CALLBACK_URL,
|
|
142
|
+
displayName: 'My App',
|
|
143
|
+
}),
|
|
144
|
+
inject: [getModelToken('Settings')],
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. Auth Guard Integration
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { SMSTUNNEL_PUBLIC_PATHS } from 'smstunnel-sdk/server';
|
|
152
|
+
|
|
153
|
+
// In your JwtAuthGuard:
|
|
154
|
+
if (SMSTUNNEL_PUBLIC_PATHS.some(p => request.url.includes(p))) return true;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### REST Endpoints
|
|
158
|
+
|
|
159
|
+
| Route | Method | Auth | Purpose |
|
|
160
|
+
|-------|--------|------|---------|
|
|
161
|
+
| `GET /smstunnel/status` | getStatus | Yes | Show pairing status |
|
|
162
|
+
| `POST /smstunnel/create-token` | createToken | Yes | Generate QR |
|
|
163
|
+
| `GET /smstunnel/pairing-status/:token` | pairingStatus | No | Polling proxy |
|
|
164
|
+
| `POST /smstunnel/callback` | callback | No | Receive API key |
|
|
165
|
+
| `POST /smstunnel/unpair` | unpair | Yes | Disconnect device |
|
|
166
|
+
| `POST /smstunnel/send` | sendSms | Yes | Send SMS |
|
|
167
|
+
| `POST /smstunnel/update-config` | updateConfig | Yes | Set server URL |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## React
|
|
172
|
+
|
|
173
|
+
Works with: **React, Next.js, Remix, Gatsby**
|
|
174
|
+
|
|
175
|
+
### Plug and Play Component
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { SmsTunnelPairing, RO_LABELS } from 'smstunnel-sdk/react';
|
|
179
|
+
|
|
180
|
+
function SettingsPage() {
|
|
181
|
+
return (
|
|
182
|
+
<SmsTunnelPairing
|
|
183
|
+
apiBaseUrl="/api"
|
|
184
|
+
getAuthHeaders={() => ({
|
|
185
|
+
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
|
186
|
+
})}
|
|
187
|
+
labels={RO_LABELS}
|
|
188
|
+
onPaired={(device) => console.log('Paired:', device)}
|
|
189
|
+
showTestSms={true}
|
|
190
|
+
showServerUrlInput={true}
|
|
191
|
+
qrSize={220}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Next.js
|
|
198
|
+
|
|
199
|
+
Add `'use client'` in your page/component:
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
'use client';
|
|
203
|
+
import { SmsTunnelPairing, RO_LABELS } from 'smstunnel-sdk/react';
|
|
204
|
+
|
|
205
|
+
export default function SmsSettings() {
|
|
206
|
+
return <SmsTunnelPairing apiBaseUrl="/api" labels={RO_LABELS} />;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Custom UI with Hook
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
import { useSmsTunnel, QrCodeCanvas } from 'smstunnel-sdk/react';
|
|
214
|
+
|
|
215
|
+
function MySmsSettings() {
|
|
216
|
+
const tunnel = useSmsTunnel({
|
|
217
|
+
apiBaseUrl: '/api',
|
|
218
|
+
getAuthHeaders: () => ({ Authorization: `Bearer ${token}` }),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (tunnel.status === 'paired') {
|
|
222
|
+
return <div>Connected: {tunnel.deviceName}</div>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div>
|
|
227
|
+
{tunnel.showQr ? (
|
|
228
|
+
<>
|
|
229
|
+
<QrCodeCanvas value={tunnel.qrData} size={200} />
|
|
230
|
+
<button onClick={tunnel.cancelPairing}>Cancel</button>
|
|
231
|
+
</>
|
|
232
|
+
) : (
|
|
233
|
+
<button onClick={tunnel.generateQr} disabled={tunnel.generating}>
|
|
234
|
+
Connect Phone
|
|
235
|
+
</button>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Vue
|
|
245
|
+
|
|
246
|
+
Works with: **Vue 3, Nuxt 3**
|
|
247
|
+
|
|
248
|
+
### Plug and Play Component
|
|
249
|
+
|
|
250
|
+
```vue
|
|
251
|
+
<script setup>
|
|
252
|
+
import { SmsTunnelPairing, RO_LABELS } from 'smstunnel-sdk/vue';
|
|
253
|
+
|
|
254
|
+
function onPaired(device) {
|
|
255
|
+
console.log('Paired:', device);
|
|
256
|
+
}
|
|
257
|
+
</script>
|
|
258
|
+
|
|
259
|
+
<template>
|
|
260
|
+
<SmsTunnelPairing
|
|
261
|
+
api-base-url="/api"
|
|
262
|
+
:labels="RO_LABELS"
|
|
263
|
+
:show-test-sms="true"
|
|
264
|
+
:show-server-url-input="true"
|
|
265
|
+
@paired="onPaired"
|
|
266
|
+
/>
|
|
267
|
+
</template>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Nuxt 3
|
|
271
|
+
|
|
272
|
+
Wrap in `<ClientOnly>` since pairing uses browser APIs:
|
|
273
|
+
|
|
274
|
+
```vue
|
|
275
|
+
<template>
|
|
276
|
+
<ClientOnly>
|
|
277
|
+
<SmsTunnelPairing api-base-url="/api" :labels="RO_LABELS" />
|
|
278
|
+
</ClientOnly>
|
|
279
|
+
</template>
|
|
280
|
+
|
|
281
|
+
<script setup>
|
|
282
|
+
import { SmsTunnelPairing, RO_LABELS } from 'smstunnel-sdk/vue';
|
|
283
|
+
</script>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Custom UI with Composable
|
|
287
|
+
|
|
288
|
+
```vue
|
|
289
|
+
<script setup>
|
|
290
|
+
import { useSmsTunnel, QrCodeCanvas } from 'smstunnel-sdk/vue';
|
|
291
|
+
|
|
292
|
+
const tunnel = useSmsTunnel({
|
|
293
|
+
apiBaseUrl: '/api',
|
|
294
|
+
getAuthHeaders: () => ({ Authorization: `Bearer ${token}` }),
|
|
295
|
+
});
|
|
296
|
+
</script>
|
|
297
|
+
|
|
298
|
+
<template>
|
|
299
|
+
<div v-if="tunnel.status.value === 'paired'">
|
|
300
|
+
Connected: {{ tunnel.deviceName.value }}
|
|
301
|
+
<button @click="tunnel.unpair()">Disconnect</button>
|
|
302
|
+
</div>
|
|
303
|
+
<div v-else>
|
|
304
|
+
<div v-if="tunnel.showQr.value">
|
|
305
|
+
<QrCodeCanvas :value="tunnel.qrData.value" :size="200" />
|
|
306
|
+
<button @click="tunnel.cancelPairing()">Cancel</button>
|
|
307
|
+
</div>
|
|
308
|
+
<button v-else @click="tunnel.generateQr()" :disabled="tunnel.generating.value">
|
|
309
|
+
Connect Phone
|
|
310
|
+
</button>
|
|
311
|
+
</div>
|
|
312
|
+
</template>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Angular
|
|
318
|
+
|
|
319
|
+
Use the framework-agnostic `SmsTunnelClient` class:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { Injectable, OnDestroy } from '@angular/core';
|
|
323
|
+
import { SmsTunnelClient } from 'smstunnel-sdk';
|
|
324
|
+
|
|
325
|
+
@Injectable({ providedIn: 'root' })
|
|
326
|
+
export class SmsTunnelService implements OnDestroy {
|
|
327
|
+
private client = new SmsTunnelClient({
|
|
328
|
+
apiBaseUrl: '/api',
|
|
329
|
+
getAuthHeaders: () => ({
|
|
330
|
+
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
|
331
|
+
}),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
getStatus() { return this.client.getStatus(); }
|
|
335
|
+
createToken() { return this.client.createToken(); }
|
|
336
|
+
startPolling(token: string, cb: any) { return this.client.startPolling(token, cb); }
|
|
337
|
+
sendSms(to: string, msg: string) { return this.client.sendSms(to, msg); }
|
|
338
|
+
unpair() { return this.client.unpair(); }
|
|
339
|
+
updateServerUrl(url: string) { return this.client.updateServerUrl(url); }
|
|
340
|
+
|
|
341
|
+
ngOnDestroy() { this.client.destroy(); }
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// Component
|
|
347
|
+
@Component({ template: `...` })
|
|
348
|
+
export class SmsPairingComponent {
|
|
349
|
+
constructor(private sms: SmsTunnelService) {}
|
|
350
|
+
|
|
351
|
+
async connect() {
|
|
352
|
+
const result = await this.sms.createToken();
|
|
353
|
+
if (result.success) {
|
|
354
|
+
this.qrData = result.qrData;
|
|
355
|
+
this.sms.startPolling(result.token!, (event) => {
|
|
356
|
+
if (event === 'completed') this.loadStatus();
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Svelte
|
|
366
|
+
|
|
367
|
+
```svelte
|
|
368
|
+
<script>
|
|
369
|
+
import { SmsTunnelClient } from 'smstunnel-sdk';
|
|
370
|
+
import { onMount, onDestroy } from 'svelte';
|
|
371
|
+
|
|
372
|
+
let status = 'loading';
|
|
373
|
+
let qrData = '';
|
|
374
|
+
let deviceName = '';
|
|
375
|
+
|
|
376
|
+
const client = new SmsTunnelClient({
|
|
377
|
+
apiBaseUrl: '/api',
|
|
378
|
+
getAuthHeaders: () => ({
|
|
379
|
+
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
onMount(async () => {
|
|
384
|
+
const s = await client.getStatus();
|
|
385
|
+
status = s.paired ? 'paired' : 'unpaired';
|
|
386
|
+
deviceName = s.deviceName || '';
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
onDestroy(() => client.destroy());
|
|
390
|
+
|
|
391
|
+
async function connect() {
|
|
392
|
+
const result = await client.createToken();
|
|
393
|
+
if (result.success) {
|
|
394
|
+
qrData = result.qrData;
|
|
395
|
+
client.startPolling(result.token, async (event) => {
|
|
396
|
+
if (event === 'completed') {
|
|
397
|
+
const s = await client.getStatus();
|
|
398
|
+
status = 'paired';
|
|
399
|
+
deviceName = s.deviceName || '';
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
</script>
|
|
405
|
+
|
|
406
|
+
{#if status === 'paired'}
|
|
407
|
+
<p>Connected: {deviceName}</p>
|
|
408
|
+
<button on:click={() => client.unpair()}>Disconnect</button>
|
|
409
|
+
{:else}
|
|
410
|
+
<button on:click={connect}>Connect Phone</button>
|
|
411
|
+
{/if}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Astro
|
|
417
|
+
|
|
418
|
+
Astro supports React, Vue, and Svelte islands. Use any of the above:
|
|
419
|
+
|
|
420
|
+
```astro
|
|
421
|
+
---
|
|
422
|
+
// pages/settings.astro
|
|
423
|
+
---
|
|
424
|
+
<script>
|
|
425
|
+
import { SmsTunnelPairing, RO_LABELS } from 'smstunnel-sdk/react';
|
|
426
|
+
</script>
|
|
427
|
+
|
|
428
|
+
<SmsTunnelPairing client:only="react" apiBaseUrl="/api" labels={RO_LABELS} />
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Or with Vue:
|
|
432
|
+
|
|
433
|
+
```astro
|
|
434
|
+
<SmsTunnelPairing client:only="vue" api-base-url="/api" />
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## SmsTunnelClient API (framework-agnostic)
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { SmsTunnelClient } from 'smstunnel-sdk';
|
|
443
|
+
|
|
444
|
+
const client = new SmsTunnelClient({
|
|
445
|
+
apiBaseUrl: '/api',
|
|
446
|
+
getAuthHeaders: () => ({ Authorization: 'Bearer ...' }),
|
|
447
|
+
routePrefix: 'smstunnel', // default
|
|
448
|
+
pollInterval: 3000, // default
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await client.getStatus(); // { paired, serverUrl, deviceName }
|
|
452
|
+
await client.createToken(); // { success, token, qrData, ... }
|
|
453
|
+
await client.getPairingStatus(token); // { status, displayName }
|
|
454
|
+
client.startPolling(token, (event) => ...); // returns cleanup fn
|
|
455
|
+
client.stopPolling();
|
|
456
|
+
await client.unpair();
|
|
457
|
+
await client.sendSms('+40741234567', 'Hi');
|
|
458
|
+
await client.updateServerUrl('https://...');
|
|
459
|
+
client.destroy(); // cleanup
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Labels (i18n)
|
|
465
|
+
|
|
466
|
+
Built-in presets: `EN_LABELS` (default), `RO_LABELS`.
|
|
467
|
+
|
|
468
|
+
Available from any import path:
|
|
469
|
+
```typescript
|
|
470
|
+
import { EN_LABELS, RO_LABELS } from 'smstunnel-sdk';
|
|
471
|
+
import { EN_LABELS, RO_LABELS } from 'smstunnel-sdk/react';
|
|
472
|
+
import { EN_LABELS, RO_LABELS } from 'smstunnel-sdk/vue';
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Custom labels: implement the `SmsTunnelLabels` interface.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Exports Summary
|
|
480
|
+
|
|
481
|
+
| Path | Exports |
|
|
482
|
+
|------|---------|
|
|
483
|
+
| `smstunnel-sdk` | `SmsTunnelClient`, `EN_LABELS`, `RO_LABELS`, types |
|
|
484
|
+
| `smstunnel-sdk/server` | `SmsTunnelModule`, `SmsTunnelService`, `SMSTUNNEL_PUBLIC_PATHS` |
|
|
485
|
+
| `smstunnel-sdk/react` | `SmsTunnelPairing`, `QrCodeCanvas`, `useSmsTunnel` |
|
|
486
|
+
| `smstunnel-sdk/vue` | `SmsTunnelPairing`, `QrCodeCanvas`, `useSmsTunnel` |
|
|
487
|
+
|
|
488
|
+
## License
|
|
489
|
+
|
|
490
|
+
MIT
|