@symbo.ls/sdk 3.1.1 → 3.2.3
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 +174 -13
- package/dist/cjs/config/environment.js +32 -42
- package/dist/cjs/index.js +31 -24
- package/dist/cjs/services/AIService.js +3 -3
- package/dist/cjs/services/AuthService.js +44 -3
- package/dist/cjs/services/BasedService.js +530 -24
- package/dist/cjs/services/CollabService.js +420 -0
- package/dist/cjs/services/CoreService.js +2295 -0
- package/dist/cjs/services/SocketService.js +207 -59
- package/dist/cjs/services/SymstoryService.js +135 -49
- package/dist/cjs/services/index.js +8 -16
- package/dist/cjs/state/RootStateManager.js +86 -0
- package/dist/cjs/state/rootEventBus.js +65 -0
- package/dist/cjs/utils/CollabClient.js +157 -0
- package/dist/cjs/utils/TokenManager.js +409 -0
- package/dist/cjs/utils/basedQuerys.js +120 -0
- package/dist/cjs/utils/jsonDiff.js +103 -0
- package/dist/cjs/utils/permission.js +4 -4
- package/dist/cjs/utils/services.js +133 -69
- package/dist/cjs/utils/symstoryClient.js +33 -2
- package/dist/esm/config/environment.js +32 -42
- package/dist/esm/index.js +20586 -11525
- package/dist/esm/services/AIService.js +3 -3
- package/dist/esm/services/AuthService.js +48 -7
- package/dist/esm/services/BasedService.js +676 -65
- package/dist/esm/services/CollabService.js +18028 -0
- package/dist/esm/services/CoreService.js +2827 -0
- package/dist/esm/services/SocketService.js +323 -58
- package/dist/esm/services/SymstoryService.js +287 -111
- package/dist/esm/services/index.js +20456 -11470
- package/dist/esm/state/RootStateManager.js +102 -0
- package/dist/esm/state/rootEventBus.js +47 -0
- package/dist/esm/utils/CollabClient.js +17483 -0
- package/dist/esm/utils/TokenManager.js +395 -0
- package/dist/esm/utils/basedQuerys.js +120 -0
- package/dist/esm/utils/jsonDiff.js +6096 -0
- package/dist/esm/utils/permission.js +4 -4
- package/dist/esm/utils/services.js +133 -69
- package/dist/esm/utils/symstoryClient.js +63 -43
- package/dist/esm/utils/validation.js +89 -19
- package/dist/node/config/environment.js +32 -42
- package/dist/node/index.js +37 -28
- package/dist/node/services/AIService.js +3 -3
- package/dist/node/services/AuthService.js +44 -3
- package/dist/node/services/BasedService.js +531 -25
- package/dist/node/services/CollabService.js +401 -0
- package/dist/node/services/CoreService.js +2266 -0
- package/dist/node/services/SocketService.js +197 -59
- package/dist/node/services/SymstoryService.js +135 -49
- package/dist/node/services/index.js +8 -16
- package/dist/node/state/RootStateManager.js +57 -0
- package/dist/node/state/rootEventBus.js +46 -0
- package/dist/node/utils/CollabClient.js +128 -0
- package/dist/node/utils/TokenManager.js +390 -0
- package/dist/node/utils/basedQuerys.js +120 -0
- package/dist/node/utils/jsonDiff.js +74 -0
- package/dist/node/utils/permission.js +4 -4
- package/dist/node/utils/services.js +133 -69
- package/dist/node/utils/symstoryClient.js +33 -2
- package/package.json +23 -14
- package/src/config/environment.js +33 -42
- package/src/index.js +45 -28
- package/src/services/AIService.js +3 -3
- package/src/services/AuthService.js +52 -3
- package/src/services/BasedService.js +603 -23
- package/src/services/CollabService.js +491 -0
- package/src/services/CoreService.js +2548 -0
- package/src/services/SocketService.js +227 -59
- package/src/services/SymstoryService.js +150 -64
- package/src/services/index.js +7 -14
- package/src/state/RootStateManager.js +71 -0
- package/src/state/rootEventBus.js +48 -0
- package/src/utils/CollabClient.js +161 -0
- package/src/utils/TokenManager.js +462 -0
- package/src/utils/basedQuerys.js +123 -0
- package/src/utils/jsonDiff.js +109 -0
- package/src/utils/permission.js +4 -4
- package/src/utils/services.js +144 -69
- package/src/utils/symstoryClient.js +36 -2
- package/dist/cjs/services/SocketIOService.js +0 -309
- package/dist/esm/services/SocketIOService.js +0 -467
- package/dist/node/services/SocketIOService.js +0 -280
- package/src/services/SocketIOService.js +0 -356
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isDevelopment } from '@domql/utils'
|
|
2
|
+
|
|
1
3
|
// Base configuration with defaults and environment-specific overrides
|
|
2
4
|
const CONFIG = {
|
|
3
5
|
// Common defaults for all environments
|
|
@@ -15,12 +17,11 @@ const CONFIG = {
|
|
|
15
17
|
// Environment-specific configurations
|
|
16
18
|
|
|
17
19
|
local: {
|
|
18
|
-
|
|
20
|
+
// local
|
|
19
21
|
socketUrl: 'http://localhost:8080', // For socket api
|
|
20
|
-
|
|
21
|
-
apiUrl: 'http://localhost:13335', // For server api
|
|
22
|
+
apiUrl: 'http://localhost:8080', // For server api
|
|
22
23
|
basedEnv: 'development', // For based api
|
|
23
|
-
basedProject: 'platform-v2', // For based api
|
|
24
|
+
basedProject: 'platform-v2-sm', // For based api
|
|
24
25
|
basedOrg: 'symbols', // For based api
|
|
25
26
|
githubClientId: 'Ov23liHxyWFBxS8f1gnF', // For github api
|
|
26
27
|
// Environment-specific feature toggles (override common)
|
|
@@ -28,34 +29,37 @@ const CONFIG = {
|
|
|
28
29
|
betaFeatures: true // Enable beta features in local dev
|
|
29
30
|
}
|
|
30
31
|
},
|
|
31
|
-
|
|
32
32
|
development: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
socketUrl: 'https://dev.api.symbols.app',
|
|
34
|
+
apiUrl: 'https://dev.api.symbols.app',
|
|
35
|
+
githubClientId: 'Ov23liHxyWFBxS8f1gnF'
|
|
36
|
+
},
|
|
37
|
+
testing: {
|
|
38
|
+
socketUrl: 'https://test.api.symbols.app',
|
|
39
|
+
apiUrl: 'https://test.api.symbols.app',
|
|
40
|
+
basedEnv: 'testing',
|
|
41
|
+
basedProject: 'platform-v2-sm',
|
|
39
42
|
basedOrg: 'symbols',
|
|
40
43
|
githubClientId: 'Ov23liHxyWFBxS8f1gnF'
|
|
41
44
|
},
|
|
45
|
+
upcoming: {
|
|
46
|
+
socketUrl: 'https://upcoming.api.symbols.app',
|
|
47
|
+
apiUrl: 'https://upcoming.api.symbols.app',
|
|
48
|
+
githubClientId: 'Ov23liWF7NvdZ056RV5J'
|
|
49
|
+
},
|
|
42
50
|
staging: {
|
|
43
|
-
|
|
44
|
-
socketUrl: 'https://staging.socket.symbols.app',
|
|
45
|
-
routerUrl: 'https://staging.router.symbols.app',
|
|
51
|
+
socketUrl: 'https://staging.api.symbols.app',
|
|
46
52
|
apiUrl: 'https://staging.api.symbols.app',
|
|
47
53
|
basedEnv: 'staging',
|
|
48
|
-
basedProject: 'platform-v2',
|
|
54
|
+
basedProject: 'platform-v2-sm',
|
|
49
55
|
basedOrg: 'symbols',
|
|
50
56
|
githubClientId: 'Ov23ligwZDQVD0VfuWNa'
|
|
51
57
|
},
|
|
52
58
|
production: {
|
|
53
|
-
|
|
54
|
-
socketUrl: 'https://socket.symbols.app',
|
|
55
|
-
routerUrl: 'https://router.symbols.app',
|
|
59
|
+
socketUrl: 'https://api.symbols.app',
|
|
56
60
|
apiUrl: 'https://api.symbols.app',
|
|
57
61
|
basedEnv: 'production',
|
|
58
|
-
basedProject: 'platform-v2',
|
|
62
|
+
basedProject: 'platform-v2-sm',
|
|
59
63
|
basedOrg: 'symbols',
|
|
60
64
|
githubClientId: 'Ov23liFAlOEIXtX3dBtR'
|
|
61
65
|
}
|
|
@@ -63,20 +67,19 @@ const CONFIG = {
|
|
|
63
67
|
|
|
64
68
|
// Determine environment with error handling
|
|
65
69
|
const getEnvironment = () => {
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
// @preserve-env
|
|
71
|
+
const env = process.env.SYMBOLS_APP_ENV || process.env.NODE_ENV
|
|
68
72
|
|
|
69
73
|
// Validate that the environment exists in our config
|
|
70
74
|
if (!CONFIG[env]) {
|
|
71
|
-
|
|
72
|
-
return 'development'
|
|
75
|
+
throw new Error(`Unknown environment "${env}"`)
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
return env
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
// Get configuration with environment variable overrides and error handling
|
|
79
|
-
const getConfig = () => {
|
|
82
|
+
export const getConfig = () => {
|
|
80
83
|
try {
|
|
81
84
|
const env = getEnvironment()
|
|
82
85
|
const envConfig = { ...CONFIG.common, ...CONFIG[env] }
|
|
@@ -84,9 +87,7 @@ const getConfig = () => {
|
|
|
84
87
|
// Create the final config with environment variable overrides
|
|
85
88
|
const finalConfig = {
|
|
86
89
|
...envConfig,
|
|
87
|
-
baseUrl: process.env.SYMBOLS_APP_BASE_URL || envConfig.baseUrl,
|
|
88
90
|
socketUrl: process.env.SYMBOLS_APP_SOCKET_URL || envConfig.socketUrl,
|
|
89
|
-
routerUrl: process.env.SYMBOLS_APP_ROUTER_URL || envConfig.routerUrl,
|
|
90
91
|
apiUrl: process.env.SYMBOLS_APP_API_URL || envConfig.apiUrl,
|
|
91
92
|
basedEnv: process.env.SYMBOLS_APP_BASED_ENV || envConfig.basedEnv,
|
|
92
93
|
basedProject:
|
|
@@ -94,22 +95,21 @@ const getConfig = () => {
|
|
|
94
95
|
basedOrg: process.env.SYMBOLS_APP_BASED_ORG || envConfig.basedOrg,
|
|
95
96
|
githubClientId:
|
|
96
97
|
process.env.SYMBOLS_APP_GITHUB_CLIENT_ID || envConfig.githubClientId,
|
|
97
|
-
isDevelopment: env
|
|
98
|
+
isDevelopment: isDevelopment(env),
|
|
99
|
+
isTesting: env === 'testing',
|
|
98
100
|
isStaging: env === 'staging',
|
|
99
101
|
isProduction: env === 'production'
|
|
102
|
+
// Store all environment variables for potential future use
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
// Validate critical configuration values
|
|
103
106
|
const requiredFields = [
|
|
104
|
-
'baseUrl',
|
|
105
107
|
'socketUrl',
|
|
106
108
|
'apiUrl',
|
|
107
|
-
'basedEnv',
|
|
108
|
-
'basedProject',
|
|
109
|
-
'basedOrg',
|
|
110
109
|
'githubClientId',
|
|
111
110
|
'googleClientId'
|
|
112
111
|
]
|
|
112
|
+
|
|
113
113
|
const missingFields = requiredFields.filter(field => !finalConfig[field])
|
|
114
114
|
|
|
115
115
|
if (missingFields.length > 0) {
|
|
@@ -121,9 +121,7 @@ const getConfig = () => {
|
|
|
121
121
|
if (finalConfig.isDevelopment) {
|
|
122
122
|
console.log(
|
|
123
123
|
'environment in SDK:',
|
|
124
|
-
env
|
|
125
|
-
process.env.NODE_ENV,
|
|
126
|
-
process.env.SYMBOLS_APP_ENV
|
|
124
|
+
env || process.env.NODE_ENV || process.env.NODE_ENV
|
|
127
125
|
)
|
|
128
126
|
console.log(finalConfig)
|
|
129
127
|
}
|
|
@@ -134,14 +132,7 @@ const getConfig = () => {
|
|
|
134
132
|
|
|
135
133
|
// Return safe fallback values to prevent crashes
|
|
136
134
|
return {
|
|
137
|
-
|
|
138
|
-
socketUrl: 'https://socket.symbols.app',
|
|
139
|
-
routerUrl: 'https://router.symbols.app',
|
|
140
|
-
apiUrl: 'https://api.symbols.app',
|
|
141
|
-
basedEnv: 'development',
|
|
142
|
-
basedProject: 'platform-v2',
|
|
143
|
-
basedOrg: 'symbols',
|
|
144
|
-
githubClientId: 'Ov23liHxyWFBxS8f1gnF'
|
|
135
|
+
...CONFIG.development
|
|
145
136
|
}
|
|
146
137
|
}
|
|
147
138
|
}
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createBasedService,
|
|
3
|
-
createSymstoryService,
|
|
4
2
|
createAuthService,
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
createCoreService,
|
|
4
|
+
createCollabService
|
|
7
5
|
} from './services/index.js'
|
|
8
6
|
|
|
9
7
|
import { SERVICE_METHODS } from './utils/services.js'
|
|
10
|
-
import { SymstoryService } from './services/SymstoryService.js'
|
|
11
8
|
import environment from './config/environment.js'
|
|
12
9
|
|
|
13
10
|
export class SDK {
|
|
@@ -20,21 +17,16 @@ export class SDK {
|
|
|
20
17
|
this._createServiceProxies()
|
|
21
18
|
}
|
|
22
19
|
|
|
20
|
+
// Initialize SDK with context
|
|
23
21
|
async initialize (context = {}) {
|
|
24
22
|
this._context = {
|
|
25
23
|
...this._context,
|
|
26
24
|
...context
|
|
27
25
|
}
|
|
28
26
|
|
|
27
|
+
//
|
|
29
28
|
// Initialize services with context
|
|
30
29
|
await Promise.all([
|
|
31
|
-
this._initService(
|
|
32
|
-
'based',
|
|
33
|
-
createBasedService({
|
|
34
|
-
context: this._context,
|
|
35
|
-
options: this._options
|
|
36
|
-
})
|
|
37
|
-
),
|
|
38
30
|
this._initService(
|
|
39
31
|
'auth',
|
|
40
32
|
createAuthService({
|
|
@@ -43,22 +35,15 @@ export class SDK {
|
|
|
43
35
|
})
|
|
44
36
|
),
|
|
45
37
|
this._initService(
|
|
46
|
-
'
|
|
47
|
-
|
|
38
|
+
'core',
|
|
39
|
+
createCoreService({
|
|
48
40
|
context: this._context,
|
|
49
41
|
options: this._options
|
|
50
42
|
})
|
|
51
43
|
),
|
|
52
44
|
this._initService(
|
|
53
|
-
'
|
|
54
|
-
|
|
55
|
-
context: this._context,
|
|
56
|
-
options: this._options
|
|
57
|
-
})
|
|
58
|
-
),
|
|
59
|
-
this._initService(
|
|
60
|
-
'ai',
|
|
61
|
-
createAIService({
|
|
45
|
+
'collab',
|
|
46
|
+
createCollabService({
|
|
62
47
|
context: this._context,
|
|
63
48
|
options: this._options
|
|
64
49
|
})
|
|
@@ -88,7 +73,7 @@ export class SDK {
|
|
|
88
73
|
_validateOptions (options) {
|
|
89
74
|
const defaults = {
|
|
90
75
|
useNewServices: true, // Use new service implementations by default
|
|
91
|
-
|
|
76
|
+
apiUrl: environment.apiUrl,
|
|
92
77
|
socketUrl: environment.socketUrl,
|
|
93
78
|
timeout: 30000,
|
|
94
79
|
retryAttempts: 3,
|
|
@@ -116,15 +101,13 @@ export class SDK {
|
|
|
116
101
|
// Update context for all services
|
|
117
102
|
for (const service of this._services.values()) {
|
|
118
103
|
service.updateContext(this._context)
|
|
119
|
-
if (service instanceof SymstoryService) {
|
|
120
|
-
service.init()
|
|
121
|
-
}
|
|
122
104
|
}
|
|
123
105
|
}
|
|
124
106
|
|
|
125
107
|
// Check if SDK is ready
|
|
126
108
|
isReady () {
|
|
127
|
-
|
|
109
|
+
const sdkServices = Array.from(this._services.values())
|
|
110
|
+
return sdkServices.length > 0 && sdkServices.every(service =>
|
|
128
111
|
service.isReady()
|
|
129
112
|
)
|
|
130
113
|
}
|
|
@@ -159,9 +142,43 @@ export class SDK {
|
|
|
159
142
|
}
|
|
160
143
|
}
|
|
161
144
|
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Destroys all services and cleans up resources
|
|
148
|
+
* @returns {Promise<boolean>} Returns true when cleanup is complete
|
|
149
|
+
*/
|
|
150
|
+
async destroy() {
|
|
151
|
+
try {
|
|
152
|
+
// Call destroy on all services
|
|
153
|
+
const destroyPromises = Array.from(this._services.entries())
|
|
154
|
+
.filter(([, service]) => typeof service.destroy === 'function')
|
|
155
|
+
.map(async ([name, service]) => {
|
|
156
|
+
await service.destroy();
|
|
157
|
+
console.log(`Service ${name} destroyed successfully`);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await Promise.all(destroyPromises);
|
|
161
|
+
|
|
162
|
+
// Clear services and reset state
|
|
163
|
+
this._services.clear();
|
|
164
|
+
this._context = {};
|
|
165
|
+
|
|
166
|
+
return true;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error('Error during SDK destruction:', error);
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
162
172
|
}
|
|
163
173
|
|
|
164
174
|
export default SDK
|
|
165
175
|
|
|
176
|
+
// Export services for direct usage
|
|
177
|
+
export {
|
|
178
|
+
createAuthService,
|
|
179
|
+
createCoreService,
|
|
180
|
+
createCollabService
|
|
181
|
+
} from './services/index.js'
|
|
182
|
+
|
|
166
183
|
// Export environment configuration
|
|
167
184
|
export { default as environment } from './config/environment.js'
|
|
@@ -6,7 +6,7 @@ export class AIService extends BaseService {
|
|
|
6
6
|
this._client = null
|
|
7
7
|
this._initialized = false
|
|
8
8
|
this._defaultConfig = {
|
|
9
|
-
|
|
9
|
+
apiUrl: 'https://api.openai.com/v1/engines/text-curie/completions',
|
|
10
10
|
temperature: 0.0,
|
|
11
11
|
maxTokens: 2000,
|
|
12
12
|
headers: {
|
|
@@ -57,7 +57,7 @@ export class AIService extends BaseService {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
validateConfig (config = {}) {
|
|
60
|
-
const requiredFields = ['
|
|
60
|
+
const requiredFields = ['apiUrl', 'temperature', 'maxTokens']
|
|
61
61
|
const missingFields = requiredFields.filter(field => !config[field])
|
|
62
62
|
|
|
63
63
|
if (missingFields.length > 0) {
|
|
@@ -89,7 +89,7 @@ export class AIService extends BaseService {
|
|
|
89
89
|
})
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
const response = await this._request(config.
|
|
92
|
+
const response = await this._request(config.apiUrl, options)
|
|
93
93
|
return response
|
|
94
94
|
} catch (error) {
|
|
95
95
|
throw new Error(`AI prompt failed: ${error.message}`)
|
|
@@ -106,16 +106,63 @@ export class AuthService extends BaseService {
|
|
|
106
106
|
async googleAuth (idToken) {
|
|
107
107
|
try {
|
|
108
108
|
const based = this._getBasedService('googleAuth')
|
|
109
|
-
|
|
109
|
+
const response = await based.call('users:google-auth', { idToken })
|
|
110
|
+
|
|
111
|
+
if (this._initialized) {
|
|
112
|
+
this.updateContext({ authToken: response.token })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
based.setAuthState({
|
|
116
|
+
token: response.token,
|
|
117
|
+
userId: response.userId,
|
|
118
|
+
persistent: true
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return response
|
|
110
122
|
} catch (error) {
|
|
111
123
|
throw new Error(`Google auth failed: ${error.message}`)
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
async googleAuthCallback (code, redirectUri) {
|
|
128
|
+
try {
|
|
129
|
+
const based = this._getBasedService('googleAuthCallback')
|
|
130
|
+
const response = await based.call('users:google-auth-callback', {
|
|
131
|
+
code,
|
|
132
|
+
redirectUri
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
if (this._initialized) {
|
|
136
|
+
this.updateContext({ authToken: response.token })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
based.setAuthState({
|
|
140
|
+
token: response.token,
|
|
141
|
+
userId: response.userId,
|
|
142
|
+
persistent: true
|
|
143
|
+
})
|
|
144
|
+
return response
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new Error(`Google auth callback failed: ${error.message}`)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
115
150
|
async githubAuth (code) {
|
|
116
151
|
try {
|
|
117
152
|
const based = this._getBasedService('githubAuth')
|
|
118
|
-
|
|
153
|
+
const response = await based.call('users:github-auth', { code })
|
|
154
|
+
|
|
155
|
+
if (this._initialized) {
|
|
156
|
+
this.updateContext({ authToken: response.token })
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
based.setAuthState({
|
|
160
|
+
token: response.token,
|
|
161
|
+
userId: response.userId,
|
|
162
|
+
persistent: true
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return response
|
|
119
166
|
} catch (error) {
|
|
120
167
|
throw new Error(`GitHub auth failed: ${error.message}`)
|
|
121
168
|
}
|
|
@@ -125,7 +172,7 @@ export class AuthService extends BaseService {
|
|
|
125
172
|
this._requireReady('logout')
|
|
126
173
|
try {
|
|
127
174
|
const based = this._getBasedService('logout')
|
|
128
|
-
await based.call('logout')
|
|
175
|
+
await based.call('users:logout')
|
|
129
176
|
this.updateContext({ authToken: null })
|
|
130
177
|
} catch (error) {
|
|
131
178
|
throw new Error(`Logout failed: ${error.message}`)
|
|
@@ -355,6 +402,8 @@ export class AuthService extends BaseService {
|
|
|
355
402
|
const based = this._getBasedService('getProjectMembers')
|
|
356
403
|
return await based.call('projects:get-members', { projectId })
|
|
357
404
|
} catch (error) {
|
|
405
|
+
if (error.message?.includes('Authentication failed. Please try again'))
|
|
406
|
+
window.location.reload()
|
|
358
407
|
throw new Error(`Failed to get project members: ${error.message}`)
|
|
359
408
|
}
|
|
360
409
|
}
|