@open-xchange/soap-client 0.2.0 → 0.2.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/CHANGELOG.md +18 -1
- package/package.json +2 -2
- package/services/common/context.js +15 -2
- package/services/common/user.js +90 -2
- package/services/reseller/resellerUser.js +5 -5
- package/soap.js +67 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.1] - 2026-09-11
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `userService.getMultipleData(context, users)` and `userService.getData(context, user)`. `list` and `listAll` return users with only the id filled, so their results carry no `name`, `display_name` or `primaryEmail` and cannot be compared against desired state. A caller that treats a list result as observed state sees every user as missing and recreates it. `getMultipleData` hydrates a whole set in one round trip; only each entry's id is sent. Both take a user id or an object carrying one, matching `remove`, `getModuleAccess`, `exists` and `changeCapabilities` on the same service, and reject a reference naming no user rather than sending `{ id: undefined }` and getting back an opaque `InvalidDataException`. `getMultipleData` answers `[]` for an empty result, where the raw response would be `undefined` because the element is a `List<User>` that serializes to nothing, and makes no round trip at all for an empty set of ids. It is all or nothing: the operation declares `NoSuchUserException`, so one id disappearing between the `list` and the hydrate rejects the whole call, and a caller that must tolerate that has to fall back to `getData` per id itself.
|
|
12
|
+
- `userService.getUserCapabilities(context, user)` and `contextService.getContextCapabilities(id)`, returning the capability names as an array. Without them a caller can only re-apply capabilities blindly on every reconcile instead of diffing them. The middleware answers these two with a display string rather than a data structure, so the parsing sits here: it joins the set with `", "`, comma and space, and reports an empty set as the sentence `There are no capabilities set for ...`, which becomes `[]`. Handed back raw, that sentence would read as a single capability named after itself, and a freshly created context is the case that hits it.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `resellerUserService` no longer forces `name` as the login. Where an admin object carries both a `login` and a `name` that differ, its calls now send `login`, matching every other service in the package. A reseller call on a context whose admin never resolved now falls back to the configured endpoint credentials as well, instead of throwing a `TypeError` on `context.admin.name`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- An admin object in the SOAP `admin_user` shape (`{ name, password }`) now authenticates instead of sending `login: undefined`. The transport reads `auth.login`, but the SOAP admin object names the account `name` and only the `auth` element calls it `login`, so a consumer passing its admin object straight through authenticated with no login and the middleware rejected it as a terminal authentication failure. The per-call `auth` accepts both shapes now, `login` winning when both are present. The endpoint credentials given to `createProvisioningClient` are unchanged: that element is only ever `Credentials` and the master admin has no `admin_user` counterpart, so it still requires `{ login, password }` and still throws a `TypeError` at construction without one.
|
|
21
|
+
- `resellerUserService` carried the same bug from the opposite side. It built `auth: { login: context.admin.name }` itself, so an admin already in the `{ login, password }` shape authenticated with no login there. All five of its authenticated methods now send `auth: context.admin` like every other service and go through the same mapping.
|
|
22
|
+
|
|
7
23
|
## [0.2.0] - 2026-09-08
|
|
8
24
|
|
|
9
25
|
### Added
|
|
@@ -141,7 +157,8 @@ All notable changes to this project will be documented in this file.
|
|
|
141
157
|
|
|
142
158
|
- Initial release: extract SOAP client into its own library
|
|
143
159
|
|
|
144
|
-
[unreleased]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.2.
|
|
160
|
+
[unreleased]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.2.1...main
|
|
161
|
+
[0.2.1]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.2.0...soap-client-0.2.1
|
|
145
162
|
[0.2.0]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.6...soap-client-0.2.0
|
|
146
163
|
[0.1.6]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.5...soap-client-0.1.6
|
|
147
164
|
[0.1.5]: https://gitlab.com/openxchange/appsuite/web-foundation/tools/-/compare/soap-client-0.1.4...soap-client-0.1.5
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-xchange/soap-client",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "SOAP client for OX App Suite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@open-xchange/lint": "^0.3.1",
|
|
40
|
-
"vitest": "^
|
|
40
|
+
"vitest": "^5.0.0"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"lint": "eslint .",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { createClientAsync, memoizeClient, logSoapError } from '../../soap.js'
|
|
21
|
+
import { createClientAsync, memoizeClient, logSoapError, parseCapabilities } from '../../soap.js'
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Build a context service bound to a SOAP client constructor.
|
|
@@ -149,6 +149,18 @@ export function createContextService (createClient, { mxDomain } = {}) {
|
|
|
149
149
|
return (await getClient()).getDataAsync({ ctx: { id } })
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The capabilities currently in effect for a context. The middleware answers
|
|
154
|
+
* with a display string (see `parseCapabilities`), so this returns the names
|
|
155
|
+
* it lists. Without it a caller can only re-apply capabilities blindly on
|
|
156
|
+
* every reconcile, never diff them.
|
|
157
|
+
* @param {number} id The context id.
|
|
158
|
+
* @returns {Promise<string[]>} Capability names, `[]` when there are none.
|
|
159
|
+
*/
|
|
160
|
+
async function getContextCapabilities (id) {
|
|
161
|
+
return parseCapabilities(await (await getClient()).getContextCapabilitiesAsync({ ctx: { id } }))
|
|
162
|
+
}
|
|
163
|
+
|
|
152
164
|
/**
|
|
153
165
|
* This function changes the context with the specified ID.
|
|
154
166
|
* @param {Object} ctx The context to change.
|
|
@@ -158,7 +170,7 @@ export function createContextService (createClient, { mxDomain } = {}) {
|
|
|
158
170
|
return (await getClient()).changeAsync({ ctx })
|
|
159
171
|
}
|
|
160
172
|
|
|
161
|
-
return { getDefault, remove, create, changeModuleAccessByName, changeCapabilities, getModuleAccess, changeModuleAccess, list, get, change }
|
|
173
|
+
return { getDefault, remove, create, changeModuleAccessByName, changeCapabilities, getModuleAccess, changeModuleAccess, list, get, change, getContextCapabilities }
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
// Classic module exports, bound to the env-configured default endpoint.
|
|
@@ -175,3 +187,4 @@ export const changeModuleAccess = (...args) => service().changeModuleAccess(...a
|
|
|
175
187
|
export const list = (...args) => service().list(...args)
|
|
176
188
|
export const get = (...args) => service().get(...args)
|
|
177
189
|
export const change = (...args) => service().change(...args)
|
|
190
|
+
export const getContextCapabilities = (...args) => service().getContextCapabilities(...args)
|
package/services/common/user.js
CHANGED
|
@@ -18,13 +18,33 @@
|
|
|
18
18
|
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { createClientAsync, memoizeClient, logSoapError } from '../../soap.js'
|
|
21
|
+
import { createClientAsync, memoizeClient, logSoapError, parseCapabilities } from '../../soap.js'
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Build a user service bound to a SOAP client constructor.
|
|
25
25
|
* @param {(type: string) => Promise<Object>} createClient
|
|
26
26
|
* @returns {Object} The user service.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Build the `user` element of a request from whatever the caller holds.
|
|
30
|
+
*
|
|
31
|
+
* Every older method on this service takes a plain id (`remove(context, 3)`,
|
|
32
|
+
* `getModuleAccess`, `exists`, `changeCapabilities`), so a caller following the
|
|
33
|
+
* convention passes one to the newer ones too. Destructuring a number yields
|
|
34
|
+
* `{ id: undefined }`, and because `user` is `nillable minOccurs="0"` the
|
|
35
|
+
* request stays schema-valid: the mistake comes back as an opaque
|
|
36
|
+
* InvalidDataException from the middleware instead of failing here. Accept both
|
|
37
|
+
* forms, and refuse a reference that names no user at all.
|
|
38
|
+
*
|
|
39
|
+
* @param {{id: number}|number} user An id, or an object carrying one.
|
|
40
|
+
* @returns {{id: number}} The `user` element to send.
|
|
41
|
+
*/
|
|
42
|
+
function userRef (user) {
|
|
43
|
+
const id = user !== null && typeof user === 'object' ? user.id : user
|
|
44
|
+
if (id === undefined || id === null) throw new TypeError('userRef: a user id is required')
|
|
45
|
+
return { id }
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
export function createUserService (createClient) {
|
|
29
49
|
const getClient = memoizeClient(createClient, 'OXUserService')
|
|
30
50
|
|
|
@@ -125,6 +145,71 @@ export function createUserService (createClient) {
|
|
|
125
145
|
})
|
|
126
146
|
}
|
|
127
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Hydrate users. `list` and `listAll` fill only the id, so their results
|
|
150
|
+
* carry no name, display_name or email and cannot be diffed against desired
|
|
151
|
+
* state; `getMultipleData` returns the full records for a set of ids in one
|
|
152
|
+
* call. Only the id of each entry is sent, whatever else the caller holds.
|
|
153
|
+
*
|
|
154
|
+
* The response element is a `List<User>`, so an empty result serializes to
|
|
155
|
+
* zero `<return>` elements and node-soap never creates the key. That would
|
|
156
|
+
* surface as `undefined` rather than an empty list and throw in the caller's
|
|
157
|
+
* `.map`, so it is normalised here.
|
|
158
|
+
*
|
|
159
|
+
* All or nothing: the operation declares `NoSuchUserException`, so if any one
|
|
160
|
+
* id disappeared between the `list` that produced it and this call, the whole
|
|
161
|
+
* request rejects and no user is hydrated. A caller that must tolerate a
|
|
162
|
+
* concurrent deletion has to fall back to `getData` per id itself; this
|
|
163
|
+
* method will not return partial results.
|
|
164
|
+
*
|
|
165
|
+
* @param {Object} context `{ id, admin }`
|
|
166
|
+
* @param {Array<{id: number}|number>} users The users to hydrate.
|
|
167
|
+
* @returns {Promise<Array<Object>>} The full user records, `[]` for none.
|
|
168
|
+
*/
|
|
169
|
+
async function getMultipleData (context, users = []) {
|
|
170
|
+
const refs = users.map(userRef)
|
|
171
|
+
if (refs.length === 0) return []
|
|
172
|
+
return (await (await getClient()).getMultipleDataAsync({
|
|
173
|
+
ctx: { id: context.id },
|
|
174
|
+
users: refs,
|
|
175
|
+
auth: context.admin
|
|
176
|
+
})) ?? []
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Hydrate one user. Same reason as getMultipleData; prefer that one for a
|
|
181
|
+
* set, since it is a single round trip.
|
|
182
|
+
* @param {Object} context `{ id, admin }`
|
|
183
|
+
* @param {{id: number}|number} user An id, or an object carrying one.
|
|
184
|
+
* @returns {Promise<Object>} The full user record.
|
|
185
|
+
*/
|
|
186
|
+
async function getData (context, user) {
|
|
187
|
+
const ref = userRef(user)
|
|
188
|
+
return (await getClient()).getDataAsync({
|
|
189
|
+
ctx: { id: context.id },
|
|
190
|
+
user: ref,
|
|
191
|
+
auth: context.admin
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The capabilities currently in effect for a user. The middleware answers
|
|
197
|
+
* with a display string (see `parseCapabilities`), so this returns the names
|
|
198
|
+
* it lists. Without it a caller can only re-apply capabilities blindly on
|
|
199
|
+
* every reconcile, never diff them.
|
|
200
|
+
* @param {Object} context `{ id, admin }`
|
|
201
|
+
* @param {{id: number}|number} user An id, or an object carrying one.
|
|
202
|
+
* @returns {Promise<string[]>} Capability names, `[]` when there are none.
|
|
203
|
+
*/
|
|
204
|
+
async function getUserCapabilities (context, user) {
|
|
205
|
+
const ref = userRef(user)
|
|
206
|
+
return parseCapabilities(await (await getClient()).getUserCapabilitiesAsync({
|
|
207
|
+
ctx: { id: context.id },
|
|
208
|
+
user: ref,
|
|
209
|
+
auth: context.admin
|
|
210
|
+
}))
|
|
211
|
+
}
|
|
212
|
+
|
|
128
213
|
async function listAll (context, includeGuests = false, excludeUsers = []) {
|
|
129
214
|
return (await getClient()).listAsync({
|
|
130
215
|
ctx: { id: context.id },
|
|
@@ -134,7 +219,7 @@ export function createUserService (createClient) {
|
|
|
134
219
|
})
|
|
135
220
|
}
|
|
136
221
|
|
|
137
|
-
return { remove, create, change, changeByModuleAccess, changeByModuleAccessName, getModuleAccess, changeCapabilities, exists, list, listAll }
|
|
222
|
+
return { remove, create, change, changeByModuleAccess, changeByModuleAccessName, getModuleAccess, changeCapabilities, exists, list, listAll, getData, getMultipleData, getUserCapabilities }
|
|
138
223
|
}
|
|
139
224
|
|
|
140
225
|
// Classic module exports, bound to the env-configured default endpoint.
|
|
@@ -153,3 +238,6 @@ export const changeCapabilities = (...args) => service().changeCapabilities(...a
|
|
|
153
238
|
export const exists = (...args) => service().exists(...args)
|
|
154
239
|
export const list = (...args) => service().list(...args)
|
|
155
240
|
export const listAll = (...args) => service().listAll(...args)
|
|
241
|
+
export const getData = (...args) => service().getData(...args)
|
|
242
|
+
export const getMultipleData = (...args) => service().getMultipleData(...args)
|
|
243
|
+
export const getUserCapabilities = (...args) => service().getUserCapabilities(...args)
|
|
@@ -45,7 +45,7 @@ export function createResellerUserService (createClient) {
|
|
|
45
45
|
return (await getClient()).changeAsync({
|
|
46
46
|
ctx: { id: context.id },
|
|
47
47
|
usrdata,
|
|
48
|
-
auth:
|
|
48
|
+
auth: context.admin
|
|
49
49
|
})
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -57,7 +57,7 @@ export function createResellerUserService (createClient) {
|
|
|
57
57
|
return (await getClient()).getModuleAccessAsync({
|
|
58
58
|
ctx: { id: context.id },
|
|
59
59
|
user: { id: userId },
|
|
60
|
-
auth:
|
|
60
|
+
auth: context.admin
|
|
61
61
|
})
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -66,7 +66,7 @@ export function createResellerUserService (createClient) {
|
|
|
66
66
|
ctx: { id: context.id },
|
|
67
67
|
moduleAccess: Object.assign({}, currentAccess, moduleAccess),
|
|
68
68
|
user: { id: userId },
|
|
69
|
-
auth:
|
|
69
|
+
auth: context.admin
|
|
70
70
|
})
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -75,7 +75,7 @@ export function createResellerUserService (createClient) {
|
|
|
75
75
|
ctx: { id: context.id },
|
|
76
76
|
access_combination_name: accessCombinationName,
|
|
77
77
|
user: { id: userId },
|
|
78
|
-
auth:
|
|
78
|
+
auth: context.admin
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -84,7 +84,7 @@ export function createResellerUserService (createClient) {
|
|
|
84
84
|
ctx: { id: context.id },
|
|
85
85
|
usrdata,
|
|
86
86
|
access_combination_name: 'all',
|
|
87
|
-
auth:
|
|
87
|
+
auth: context.admin
|
|
88
88
|
})
|
|
89
89
|
}
|
|
90
90
|
|
package/soap.js
CHANGED
|
@@ -244,9 +244,65 @@ function handleFailedAttempt (context, label) {
|
|
|
244
244
|
* @property {string} url Base URL of the provisioning API (e.g. the core-mw
|
|
245
245
|
* admin Service), without the `/webservices` suffix.
|
|
246
246
|
* @property {{login: string, password: string}} auth Master admin credentials
|
|
247
|
-
* injected into every SOAP call unless the call passes its own `auth`.
|
|
247
|
+
* injected into every SOAP call unless the call passes its own `auth`. This
|
|
248
|
+
* one is a `Credentials` element and nothing else, so it is `login` only —
|
|
249
|
+
* see {@link AdminCredentials} for the per-call shape, which is wider.
|
|
248
250
|
*/
|
|
249
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @typedef {object} AdminCredentials
|
|
254
|
+
* @property {string} [login] The account name as the `auth` element calls it.
|
|
255
|
+
* @property {string} [name] The same account as the SOAP `admin_user` object
|
|
256
|
+
* calls it. Used when `login` is absent.
|
|
257
|
+
* @property {string} password
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Read the account name out of an admin object.
|
|
262
|
+
*
|
|
263
|
+
* The SOAP `admin_user` object names the account `name`; only the `auth`
|
|
264
|
+
* element calls it `login`. Consumers hold the former and the transport needs
|
|
265
|
+
* the latter, and every one of them used to bridge that itself: the CLI
|
|
266
|
+
* (`bin/provision.js`), `resellerUserService` and appsuite-codeceptjs's
|
|
267
|
+
* `Context` constructor each added a `login` in their own way, and anyone who
|
|
268
|
+
* forgot authenticated with `login: undefined` and got a terminal
|
|
269
|
+
* "Authentication failed" from the middleware. Reading both shapes in one
|
|
270
|
+
* place removes the trap; `login` wins when both are present, so the bridges
|
|
271
|
+
* that remain outside this package keep working unchanged.
|
|
272
|
+
*
|
|
273
|
+
* @param {AdminCredentials} [admin]
|
|
274
|
+
* @returns {string|undefined}
|
|
275
|
+
*/
|
|
276
|
+
function adminLogin (admin) {
|
|
277
|
+
return admin?.login || admin?.name
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The capability reads answer with a display string, not a data structure, so
|
|
282
|
+
* parsing belongs here rather than in every caller.
|
|
283
|
+
*
|
|
284
|
+
* `OXUserServicePortTypeImpl.getUserCapabilities` and its context twin sort the
|
|
285
|
+
* set into a `TreeSet` and join it with `", "`, comma and space, so a plain
|
|
286
|
+
* `split(',')` leaves a blank in front of every entry but the first and nothing
|
|
287
|
+
* ever compares equal to the desired state. Worse, an empty set is reported as
|
|
288
|
+
* a sentence:
|
|
289
|
+
*
|
|
290
|
+
* There are no capabilities set for user 3 in context 42
|
|
291
|
+
* There are no capabilities set for context 42
|
|
292
|
+
*
|
|
293
|
+
* Returned verbatim that becomes one capability named after the sentence, which
|
|
294
|
+
* a reconciler then tries to remove. A context that has just been created is
|
|
295
|
+
* exactly the case that hits it, so the empty answer is the common one.
|
|
296
|
+
*
|
|
297
|
+
* @param {string} [capabilities] The raw `return` of a capabilities call.
|
|
298
|
+
* @returns {string[]} The capability names, or `[]` when there are none.
|
|
299
|
+
*/
|
|
300
|
+
function parseCapabilities (capabilities) {
|
|
301
|
+
if (typeof capabilities !== 'string') return []
|
|
302
|
+
if (/^There are no capabilities set for /.test(capabilities)) return []
|
|
303
|
+
return capabilities.split(',').map(c => c.trim()).filter(Boolean)
|
|
304
|
+
}
|
|
305
|
+
|
|
250
306
|
/**
|
|
251
307
|
* Create a per-endpoint SOAP client constructor. Multiple factories coexist in
|
|
252
308
|
* one process, each bound to its own endpoint and credentials — nothing is
|
|
@@ -256,6 +312,10 @@ function handleFailedAttempt (context, label) {
|
|
|
256
312
|
*/
|
|
257
313
|
function createClientFactory ({ url, auth }) {
|
|
258
314
|
if (!url) throw new TypeError('createClientFactory: url is required')
|
|
315
|
+
// Deliberately narrower than the per-call `auth` below: this is the master
|
|
316
|
+
// admin, which has no `admin_user` counterpart, so no SOAP object names it
|
|
317
|
+
// `name`. Accepting one would only let a wrong object through construction
|
|
318
|
+
// and turn it into a terminal "Authentication failed" on the first call.
|
|
259
319
|
if (!auth?.login || !auth?.password) throw new TypeError('createClientFactory: auth {login, password} is required')
|
|
260
320
|
const baseUrl = String(url).replace(/\/$/, '')
|
|
261
321
|
const defaultAuth = { auth: { login: auth.login, password: auth.password } }
|
|
@@ -385,9 +445,11 @@ async function createTypedClientAsync (type, baseUrl, defaultAuth) {
|
|
|
385
445
|
const { auth, ...rest } = options ?? {}
|
|
386
446
|
const soapOptions = { ...defaultAuth, ...rest }
|
|
387
447
|
if (auth) {
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
|
|
448
|
+
// Send login and password only, never the whole admin object:
|
|
449
|
+
// the extra fields can fail on ambiguous namespacing. `adminLogin`
|
|
450
|
+
// reads either name the account goes by, so callers may pass an
|
|
451
|
+
// `admin_user` object straight through (see AdminCredentials).
|
|
452
|
+
soapOptions.auth = { login: adminLogin(auth), password: auth.password }
|
|
391
453
|
}
|
|
392
454
|
|
|
393
455
|
try {
|
|
@@ -412,4 +474,4 @@ async function createTypedClientAsync (type, baseUrl, defaultAuth) {
|
|
|
412
474
|
})
|
|
413
475
|
}
|
|
414
476
|
|
|
415
|
-
export { createClientAsync, createClientFactory, memoizeClient, installTimeoutRejectionFilter, logSoapError, describeError, shouldAbortRetry, wrapSoapFault, unwrapError, handleFailedAttempt }
|
|
477
|
+
export { createClientAsync, createClientFactory, adminLogin, parseCapabilities, memoizeClient, installTimeoutRejectionFilter, logSoapError, describeError, shouldAbortRetry, wrapSoapFault, unwrapError, handleFailedAttempt }
|