authfort-client 0.0.18 → 0.0.19
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 +165 -165
- package/package.json +83 -83
package/README.md
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
<picture>
|
|
4
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/bhagyajitjagdev/authfort/main/.github/logo-dark.svg" width="60">
|
|
5
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/bhagyajitjagdev/authfort/main/.github/logo-light.svg" width="60">
|
|
6
|
-
<img alt="AuthFort" src="https://raw.githubusercontent.com/bhagyajitjagdev/authfort/main/.github/logo-light.svg" width="60">
|
|
7
|
-
</picture>
|
|
8
|
-
|
|
9
|
-
# authfort-client
|
|
10
|
-
|
|
11
|
-
[](https://www.npmjs.com/package/authfort-client)
|
|
12
|
-
[](https://codecov.io/gh/bhagyajitjagdev/authfort)
|
|
13
|
-
[](https://www.typescriptlang.org/)
|
|
14
|
-
[](https://opensource.org/licenses/MIT)
|
|
15
|
-
[](https://bhagyajitjagdev.github.io/authfort/client/)
|
|
16
|
-
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
TypeScript client SDK for AuthFort authentication.
|
|
20
|
-
|
|
21
|
-
## Install
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npm install authfort-client
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
import { createAuthClient } from 'authfort-client';
|
|
31
|
-
|
|
32
|
-
const auth = createAuthClient({
|
|
33
|
-
baseUrl: '/auth',
|
|
34
|
-
tokenMode: 'cookie', // or 'bearer'
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
await auth.initialize();
|
|
38
|
-
|
|
39
|
-
// Sign up / sign in
|
|
40
|
-
await auth.signUp({ email: 'user@example.com', password: 'secret' });
|
|
41
|
-
await auth.signIn({ email: 'user@example.com', password: 'secret' });
|
|
42
|
-
|
|
43
|
-
// Authenticated fetch (auto-attaches credentials, retries on 401)
|
|
44
|
-
const res = await auth.fetch('/api/profile');
|
|
45
|
-
|
|
46
|
-
// OAuth (built-in or any generic provider)
|
|
47
|
-
auth.signInWithProvider('google');
|
|
48
|
-
auth.signInWithProvider('keycloak'); // generic providers work too
|
|
49
|
-
|
|
50
|
-
// Passwordless
|
|
51
|
-
await auth.requestMagicLink('user@example.com');
|
|
52
|
-
const user = await auth.verifyMagicLink(token);
|
|
53
|
-
|
|
54
|
-
await auth.requestOTP('user@example.com');
|
|
55
|
-
const user2 = await auth.verifyOTP('user@example.com', '123456');
|
|
56
|
-
|
|
57
|
-
// Listen for auth state changes
|
|
58
|
-
auth.onAuthStateChange((state, user) => {
|
|
59
|
-
console.log(state, user);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Sign out
|
|
63
|
-
await auth.signOut();
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## React
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
import { AuthProvider, useAuth } from 'authfort-client/react';
|
|
70
|
-
|
|
71
|
-
// Wrap your app
|
|
72
|
-
<AuthProvider client={auth}><App /></AuthProvider>
|
|
73
|
-
|
|
74
|
-
// In components
|
|
75
|
-
function Profile() {
|
|
76
|
-
const { user, isAuthenticated, client } = useAuth();
|
|
77
|
-
if (!isAuthenticated) return <p>Not signed in</p>;
|
|
78
|
-
return <p>Hello {user.email}</p>;
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Vue
|
|
83
|
-
|
|
84
|
-
```vue
|
|
85
|
-
<script setup>
|
|
86
|
-
import { provideAuth, useAuth } from 'authfort-client/vue';
|
|
87
|
-
|
|
88
|
-
provideAuth(auth); // in root component
|
|
89
|
-
|
|
90
|
-
const { user, isAuthenticated } = useAuth(); // in any child
|
|
91
|
-
</script>
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Svelte
|
|
95
|
-
|
|
96
|
-
```svelte
|
|
97
|
-
<script>
|
|
98
|
-
import { createAuthStore } from 'authfort-client/svelte';
|
|
99
|
-
|
|
100
|
-
const { user, isAuthenticated, client } = createAuthStore(auth);
|
|
101
|
-
</script>
|
|
102
|
-
|
|
103
|
-
{#if $isAuthenticated}
|
|
104
|
-
Hello {$user.email}
|
|
105
|
-
{/if}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Authenticated Requests
|
|
109
|
-
|
|
110
|
-
`auth.fetch()` is native `fetch` with auth added — same `RequestInit`, same `Response`. Headers, streaming, AbortController all work as normal.
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// JSON POST
|
|
114
|
-
const res = await auth.fetch('/api/data', {
|
|
115
|
-
method: 'POST',
|
|
116
|
-
headers: { 'Content-Type': 'application/json' },
|
|
117
|
-
body: JSON.stringify({ name: 'test' }),
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// Streaming
|
|
121
|
-
const stream = await auth.fetch('/api/stream');
|
|
122
|
-
const reader = stream.body.getReader();
|
|
123
|
-
|
|
124
|
-
// AbortController
|
|
125
|
-
const controller = new AbortController();
|
|
126
|
-
await auth.fetch('/api/slow', { signal: controller.signal });
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
If a request gets a 401, it automatically refreshes the token and retries once. Multiple concurrent 401s share a single refresh call.
|
|
130
|
-
|
|
131
|
-
### Using with Axios / TanStack Query (bearer mode)
|
|
132
|
-
|
|
133
|
-
In cookie mode, any HTTP client works out of the box — the browser sends cookies automatically. In bearer mode, if you prefer your own HTTP client over `auth.fetch()`, use `getToken()` to get a valid token:
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
// Axios interceptor
|
|
137
|
-
axios.interceptors.request.use(async (config) => {
|
|
138
|
-
const token = await auth.getToken();
|
|
139
|
-
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
140
|
-
return config;
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// TanStack Query
|
|
144
|
-
const { data } = useQuery({
|
|
145
|
-
queryKey: ['profile'],
|
|
146
|
-
queryFn: async () => {
|
|
147
|
-
const token = await auth.getToken();
|
|
148
|
-
const res = await fetch('/api/profile', {
|
|
149
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
150
|
-
});
|
|
151
|
-
return res.json();
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
`getToken()` automatically refreshes if the token is expired, and deduplicates concurrent refresh calls.
|
|
157
|
-
|
|
158
|
-
## Token Modes
|
|
159
|
-
|
|
160
|
-
- **cookie** (default) — httponly cookies, JS never touches tokens
|
|
161
|
-
- **bearer** — access token in memory, `Authorization: Bearer` header
|
|
162
|
-
|
|
163
|
-
## License
|
|
164
|
-
|
|
165
|
-
[MIT](../LICENSE)
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/bhagyajitjagdev/authfort/main/.github/logo-dark.svg" width="60">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/bhagyajitjagdev/authfort/main/.github/logo-light.svg" width="60">
|
|
6
|
+
<img alt="AuthFort" src="https://raw.githubusercontent.com/bhagyajitjagdev/authfort/main/.github/logo-light.svg" width="60">
|
|
7
|
+
</picture>
|
|
8
|
+
|
|
9
|
+
# authfort-client
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/authfort-client)
|
|
12
|
+
[](https://codecov.io/gh/bhagyajitjagdev/authfort)
|
|
13
|
+
[](https://www.typescriptlang.org/)
|
|
14
|
+
[](https://opensource.org/licenses/MIT)
|
|
15
|
+
[](https://bhagyajitjagdev.github.io/authfort/client/)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
TypeScript client SDK for AuthFort authentication.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install authfort-client
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createAuthClient } from 'authfort-client';
|
|
31
|
+
|
|
32
|
+
const auth = createAuthClient({
|
|
33
|
+
baseUrl: '/auth',
|
|
34
|
+
tokenMode: 'cookie', // or 'bearer'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await auth.initialize();
|
|
38
|
+
|
|
39
|
+
// Sign up / sign in
|
|
40
|
+
await auth.signUp({ email: 'user@example.com', password: 'secret' });
|
|
41
|
+
await auth.signIn({ email: 'user@example.com', password: 'secret' });
|
|
42
|
+
|
|
43
|
+
// Authenticated fetch (auto-attaches credentials, retries on 401)
|
|
44
|
+
const res = await auth.fetch('/api/profile');
|
|
45
|
+
|
|
46
|
+
// OAuth (built-in or any generic provider)
|
|
47
|
+
auth.signInWithProvider('google');
|
|
48
|
+
auth.signInWithProvider('keycloak'); // generic providers work too
|
|
49
|
+
|
|
50
|
+
// Passwordless
|
|
51
|
+
await auth.requestMagicLink('user@example.com');
|
|
52
|
+
const user = await auth.verifyMagicLink(token);
|
|
53
|
+
|
|
54
|
+
await auth.requestOTP('user@example.com');
|
|
55
|
+
const user2 = await auth.verifyOTP('user@example.com', '123456');
|
|
56
|
+
|
|
57
|
+
// Listen for auth state changes
|
|
58
|
+
auth.onAuthStateChange((state, user) => {
|
|
59
|
+
console.log(state, user);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Sign out
|
|
63
|
+
await auth.signOut();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## React
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { AuthProvider, useAuth } from 'authfort-client/react';
|
|
70
|
+
|
|
71
|
+
// Wrap your app
|
|
72
|
+
<AuthProvider client={auth}><App /></AuthProvider>
|
|
73
|
+
|
|
74
|
+
// In components
|
|
75
|
+
function Profile() {
|
|
76
|
+
const { user, isAuthenticated, client } = useAuth();
|
|
77
|
+
if (!isAuthenticated) return <p>Not signed in</p>;
|
|
78
|
+
return <p>Hello {user.email}</p>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Vue
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<script setup>
|
|
86
|
+
import { provideAuth, useAuth } from 'authfort-client/vue';
|
|
87
|
+
|
|
88
|
+
provideAuth(auth); // in root component
|
|
89
|
+
|
|
90
|
+
const { user, isAuthenticated } = useAuth(); // in any child
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Svelte
|
|
95
|
+
|
|
96
|
+
```svelte
|
|
97
|
+
<script>
|
|
98
|
+
import { createAuthStore } from 'authfort-client/svelte';
|
|
99
|
+
|
|
100
|
+
const { user, isAuthenticated, client } = createAuthStore(auth);
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
{#if $isAuthenticated}
|
|
104
|
+
Hello {$user.email}
|
|
105
|
+
{/if}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Authenticated Requests
|
|
109
|
+
|
|
110
|
+
`auth.fetch()` is native `fetch` with auth added — same `RequestInit`, same `Response`. Headers, streaming, AbortController all work as normal.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// JSON POST
|
|
114
|
+
const res = await auth.fetch('/api/data', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
body: JSON.stringify({ name: 'test' }),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Streaming
|
|
121
|
+
const stream = await auth.fetch('/api/stream');
|
|
122
|
+
const reader = stream.body.getReader();
|
|
123
|
+
|
|
124
|
+
// AbortController
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
await auth.fetch('/api/slow', { signal: controller.signal });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
If a request gets a 401, it automatically refreshes the token and retries once. Multiple concurrent 401s share a single refresh call.
|
|
130
|
+
|
|
131
|
+
### Using with Axios / TanStack Query (bearer mode)
|
|
132
|
+
|
|
133
|
+
In cookie mode, any HTTP client works out of the box — the browser sends cookies automatically. In bearer mode, if you prefer your own HTTP client over `auth.fetch()`, use `getToken()` to get a valid token:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// Axios interceptor
|
|
137
|
+
axios.interceptors.request.use(async (config) => {
|
|
138
|
+
const token = await auth.getToken();
|
|
139
|
+
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
140
|
+
return config;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// TanStack Query
|
|
144
|
+
const { data } = useQuery({
|
|
145
|
+
queryKey: ['profile'],
|
|
146
|
+
queryFn: async () => {
|
|
147
|
+
const token = await auth.getToken();
|
|
148
|
+
const res = await fetch('/api/profile', {
|
|
149
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
150
|
+
});
|
|
151
|
+
return res.json();
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`getToken()` automatically refreshes if the token is expired, and deduplicates concurrent refresh calls.
|
|
157
|
+
|
|
158
|
+
## Token Modes
|
|
159
|
+
|
|
160
|
+
- **cookie** (default) — httponly cookies, JS never touches tokens
|
|
161
|
+
- **bearer** — access token in memory, `Authorization: Bearer` header
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
[MIT](../LICENSE)
|
package/package.json
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "authfort-client",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "TypeScript client SDK for AuthFort authentication",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"dist"
|
|
10
|
-
],
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"import": "./dist/index.js",
|
|
14
|
-
"types": "./dist/index.d.ts"
|
|
15
|
-
},
|
|
16
|
-
"./react": {
|
|
17
|
-
"import": "./dist/react/index.js",
|
|
18
|
-
"types": "./dist/react/index.d.ts"
|
|
19
|
-
},
|
|
20
|
-
"./vue": {
|
|
21
|
-
"import": "./dist/vue/index.js",
|
|
22
|
-
"types": "./dist/vue/index.d.ts"
|
|
23
|
-
},
|
|
24
|
-
"./svelte": {
|
|
25
|
-
"import": "./dist/svelte/index.js",
|
|
26
|
-
"types": "./dist/svelte/index.d.ts"
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"build": "tsc",
|
|
31
|
-
"dev": "tsc --watch",
|
|
32
|
-
"test": "vitest run",
|
|
33
|
-
"test:watch": "vitest",
|
|
34
|
-
"coverage": "vitest run --coverage",
|
|
35
|
-
"deadcode": "knip"
|
|
36
|
-
},
|
|
37
|
-
"peerDependencies": {
|
|
38
|
-
"react": ">=18",
|
|
39
|
-
"svelte": ">=4",
|
|
40
|
-
"vue": ">=3"
|
|
41
|
-
},
|
|
42
|
-
"peerDependenciesMeta": {
|
|
43
|
-
"react": {
|
|
44
|
-
"optional": true
|
|
45
|
-
},
|
|
46
|
-
"vue": {
|
|
47
|
-
"optional": true
|
|
48
|
-
},
|
|
49
|
-
"svelte": {
|
|
50
|
-
"optional": true
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
"keywords": [
|
|
54
|
-
"auth",
|
|
55
|
-
"authentication",
|
|
56
|
-
"jwt",
|
|
57
|
-
"oauth",
|
|
58
|
-
"typescript"
|
|
59
|
-
],
|
|
60
|
-
"author": "Bhagyajit Jagdev",
|
|
61
|
-
"license": "MIT",
|
|
62
|
-
"repository": {
|
|
63
|
-
"type": "git",
|
|
64
|
-
"url": "https://github.com/bhagyajitjagdev/authfort"
|
|
65
|
-
},
|
|
66
|
-
"knip": {
|
|
67
|
-
"ignoreDependencies": ["@vitest/coverage-v8"]
|
|
68
|
-
},
|
|
69
|
-
"devDependencies": {
|
|
70
|
-
"@testing-library/react": "^16.3.2",
|
|
71
|
-
"@types/react": "^19.2.14",
|
|
72
|
-
"@vitest/coverage-v8": "^4.0.18",
|
|
73
|
-
"@vue/test-utils": "^2.2.7",
|
|
74
|
-
"jsdom": "^28.1.0",
|
|
75
|
-
"knip": "^5.85.0",
|
|
76
|
-
"react": "^19.2.4",
|
|
77
|
-
"react-dom": "^19.2.4",
|
|
78
|
-
"svelte": "^5.51.3",
|
|
79
|
-
"typescript": "^5.9.3",
|
|
80
|
-
"vitest": "^4.0.18",
|
|
81
|
-
"vue": "^3.5.28"
|
|
82
|
-
}
|
|
83
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "authfort-client",
|
|
3
|
+
"version": "0.0.19",
|
|
4
|
+
"description": "TypeScript client SDK for AuthFort authentication",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./react": {
|
|
17
|
+
"import": "./dist/react/index.js",
|
|
18
|
+
"types": "./dist/react/index.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./vue": {
|
|
21
|
+
"import": "./dist/vue/index.js",
|
|
22
|
+
"types": "./dist/vue/index.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./svelte": {
|
|
25
|
+
"import": "./dist/svelte/index.js",
|
|
26
|
+
"types": "./dist/svelte/index.d.ts"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"dev": "tsc --watch",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"coverage": "vitest run --coverage",
|
|
35
|
+
"deadcode": "knip"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": ">=18",
|
|
39
|
+
"svelte": ">=4",
|
|
40
|
+
"vue": ">=3"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"vue": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"svelte": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"auth",
|
|
55
|
+
"authentication",
|
|
56
|
+
"jwt",
|
|
57
|
+
"oauth",
|
|
58
|
+
"typescript"
|
|
59
|
+
],
|
|
60
|
+
"author": "Bhagyajit Jagdev",
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "https://github.com/bhagyajitjagdev/authfort"
|
|
65
|
+
},
|
|
66
|
+
"knip": {
|
|
67
|
+
"ignoreDependencies": ["@vitest/coverage-v8"]
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@testing-library/react": "^16.3.2",
|
|
71
|
+
"@types/react": "^19.2.14",
|
|
72
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
73
|
+
"@vue/test-utils": "^2.2.7",
|
|
74
|
+
"jsdom": "^28.1.0",
|
|
75
|
+
"knip": "^5.85.0",
|
|
76
|
+
"react": "^19.2.4",
|
|
77
|
+
"react-dom": "^19.2.4",
|
|
78
|
+
"svelte": "^5.51.3",
|
|
79
|
+
"typescript": "^5.9.3",
|
|
80
|
+
"vitest": "^4.0.18",
|
|
81
|
+
"vue": "^3.5.28"
|
|
82
|
+
}
|
|
83
|
+
}
|