@open-xchange/soap-client 0.1.6 → 0.2.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/CHANGELOG.md +24 -1
- package/README.md +23 -0
- package/client.js +72 -0
- package/package.json +18 -7
- package/services/common/context.js +144 -131
- package/services/common/user.js +121 -100
- package/services/common/util.js +23 -9
- package/services/reseller/oxaas.js +23 -11
- package/services/reseller/resellerContext.js +45 -29
- package/services/reseller/resellerUser.js +81 -63
- package/services/secondaryAccount.js +61 -46
- package/services/sharedAccount.js +120 -104
- package/soap.js +142 -43
- package/test/soap.test.js +0 -268
- package/vitest.config.js +0 -8
|
@@ -1,52 +1,67 @@
|
|
|
1
|
-
import { createClientAsync } from '../soap.js'
|
|
2
|
-
|
|
3
|
-
let OXSecondaryAccountService
|
|
4
|
-
function getClient () {
|
|
5
|
-
if (!OXSecondaryAccountService) OXSecondaryAccountService = createClientAsync('OXSecondaryAccountService')
|
|
6
|
-
return OXSecondaryAccountService
|
|
7
|
-
}
|
|
1
|
+
import { createClientAsync, memoizeClient } from '../soap.js'
|
|
8
2
|
|
|
9
3
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @
|
|
12
|
-
* @
|
|
13
|
-
* @param {Object} context - The context object where the account will be created
|
|
14
|
-
* @param {Array<Object>} users - Array of users to associate with the secondary account
|
|
15
|
-
* @param {Array<Object>} groups - Array of groups to associate with the secondary account
|
|
16
|
-
* @returns {Promise<Object>} The created secondary account object
|
|
17
|
-
* @throws {Error} If the account creation fails
|
|
4
|
+
* Build a secondary-account service bound to a SOAP client constructor.
|
|
5
|
+
* @param {(type: string) => Promise<Object>} createClient
|
|
6
|
+
* @returns {Object} The secondary account service.
|
|
18
7
|
*/
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
accountDataOnCreate: accountData,
|
|
22
|
-
context: { id: context.id },
|
|
23
|
-
users: users || [],
|
|
24
|
-
auth: context.admin
|
|
25
|
-
})
|
|
26
|
-
}
|
|
8
|
+
export function createSecondaryAccountService (createClient) {
|
|
9
|
+
const getClient = memoizeClient(createClient, 'OXSecondaryAccountService')
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
11
|
+
/**
|
|
12
|
+
* Creates a secondary account using the OX Secondary Account Service
|
|
13
|
+
* @async
|
|
14
|
+
* @param {Object} accountData - The data for the secondary account to be created
|
|
15
|
+
* @param {Object} context - The context object where the account will be created
|
|
16
|
+
* @param {Array<Object>} users - Array of users to associate with the secondary account
|
|
17
|
+
* @param {Array<Object>} groups - Array of groups to associate with the secondary account
|
|
18
|
+
* @returns {Promise<Object>} The created secondary account object
|
|
19
|
+
* @throws {Error} If the account creation fails
|
|
20
|
+
*/
|
|
21
|
+
async function create (accountData, context, users = [], groups = []) {
|
|
22
|
+
return (await getClient()).createAsync({
|
|
23
|
+
accountDataOnCreate: accountData,
|
|
24
|
+
context: { id: context.id },
|
|
25
|
+
users: users || [],
|
|
26
|
+
auth: context.admin
|
|
27
|
+
})
|
|
28
|
+
}
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
30
|
+
async function list (context) {
|
|
31
|
+
return (await getClient()).listAsync({
|
|
32
|
+
context: { id: context.id },
|
|
33
|
+
auth: context.admin
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function remove (primaryAddress, context, users, groups) {
|
|
38
|
+
return (await getClient()).deleteAsync({
|
|
39
|
+
primaryAddress,
|
|
40
|
+
context: { id: context.id },
|
|
41
|
+
users,
|
|
42
|
+
auth: context.admin
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function update (primaryAddress, accountData, context, users, groups) {
|
|
47
|
+
return (await getClient()).updateAsync({
|
|
48
|
+
primaryAddress,
|
|
49
|
+
accountDataUpdate: accountData,
|
|
50
|
+
context: { id: context.id },
|
|
51
|
+
users,
|
|
52
|
+
groups,
|
|
53
|
+
auth: context.admin
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { create, list, remove, update }
|
|
52
58
|
}
|
|
59
|
+
|
|
60
|
+
// Classic module exports, bound to the env-configured default endpoint.
|
|
61
|
+
let defaultService
|
|
62
|
+
const service = () => (defaultService ??= createSecondaryAccountService(createClientAsync))
|
|
63
|
+
|
|
64
|
+
export const create = (...args) => service().create(...args)
|
|
65
|
+
export const list = (...args) => service().list(...args)
|
|
66
|
+
export const remove = (...args) => service().remove(...args)
|
|
67
|
+
export const update = (...args) => service().update(...args)
|
|
@@ -18,116 +18,132 @@
|
|
|
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 } from '../soap.js'
|
|
22
|
-
|
|
23
|
-
let OXSharedAccountService
|
|
24
|
-
function getClient () {
|
|
25
|
-
if (!OXSharedAccountService) OXSharedAccountService = createClientAsync('OXSharedAccountService')
|
|
26
|
-
return OXSharedAccountService
|
|
27
|
-
}
|
|
21
|
+
import { createClientAsync, memoizeClient } from '../soap.js'
|
|
28
22
|
|
|
29
23
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @
|
|
33
|
-
* @param {Object} sharedAccountData - The data for the shared account to be created
|
|
34
|
-
* @returns {Promise<Object>} The created shared account object
|
|
35
|
-
* @throws {Error} If the shared account creation fails
|
|
24
|
+
* Build a shared-account service bound to a SOAP client constructor.
|
|
25
|
+
* @param {(type: string) => Promise<Object>} createClient
|
|
26
|
+
* @returns {Object} The shared account service.
|
|
36
27
|
*/
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
ctx: { id: context.id },
|
|
40
|
-
sharedAccountData, // display_name, given_name, imapServer, language, mailenabled, name, sur_name, primaryEmail, email1, smtpServer, timezone, password
|
|
41
|
-
auth: context.admin
|
|
42
|
-
})
|
|
43
|
-
}
|
|
28
|
+
export function createSharedAccountService (createClient) {
|
|
29
|
+
const getClient = memoizeClient(createClient, 'OXSharedAccountService')
|
|
44
30
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
})
|
|
61
|
-
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a shared account using the OX Shared Account Service
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} context - The context object where the account will be created
|
|
35
|
+
* @param {Object} sharedAccountData - The data for the shared account to be created
|
|
36
|
+
* @returns {Promise<Object>} The created shared account object
|
|
37
|
+
* @throws {Error} If the shared account creation fails
|
|
38
|
+
*/
|
|
39
|
+
async function create (context, sharedAccountData) {
|
|
40
|
+
return (await getClient()).createAsync({
|
|
41
|
+
ctx: { id: context.id },
|
|
42
|
+
sharedAccountData, // display_name, given_name, imapServer, language, mailenabled, name, sur_name, primaryEmail, email1, smtpServer, timezone, password
|
|
43
|
+
auth: context.admin
|
|
44
|
+
})
|
|
45
|
+
}
|
|
62
46
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
capabilities: {
|
|
81
|
-
grantedCapability: permissions.capabilities?.grantedCapability ?? [],
|
|
82
|
-
deniedCapability: permissions.capabilities?.deniedCapability ?? [],
|
|
83
|
-
},
|
|
84
|
-
mailConfig: permissions.mailConfig,
|
|
85
|
-
calendarConfig: permissions.calendarConfig,
|
|
86
|
-
auth: context.admin
|
|
87
|
-
})
|
|
88
|
-
}
|
|
47
|
+
/**
|
|
48
|
+
* Converts an existing user to a shared account using the OX Shared Account Service
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} context - The context of the user to be converted
|
|
51
|
+
* @param {number} userId - ID of the user to be converted
|
|
52
|
+
* @param {string} password - The password for the new shared account
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
* @throws {Error} If the conversion fails
|
|
55
|
+
*/
|
|
56
|
+
async function convertUserToSharedAccount (context, userId, password) {
|
|
57
|
+
return (await getClient()).convertUserToSharedAccountAsync({
|
|
58
|
+
ctx: { id: context.id },
|
|
59
|
+
user: { id: userId },
|
|
60
|
+
password,
|
|
61
|
+
auth: context.admin
|
|
62
|
+
})
|
|
63
|
+
}
|
|
89
64
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
65
|
+
/**
|
|
66
|
+
* Add users and groups with specified permissions and capabilities to an existing shared account using the OX Shared Account Service.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} context - The context of the users and groups
|
|
69
|
+
* @param {Object} sharedAccountContext - The context of the shared account
|
|
70
|
+
* @param {number} sharedAccountId - ID of the shared account
|
|
71
|
+
* @param {Object} permissions - The users, groups, capabilities, and module-specific permissions.
|
|
72
|
+
* @returns {Promise<Object>} The shared account object
|
|
73
|
+
* @throws {Error} If the creation of the shared account permissions fails
|
|
74
|
+
*/
|
|
75
|
+
async function createSharedAccountPermissions (context, sharedAccountContext, sharedAccountId, permissions) {
|
|
76
|
+
return (await getClient()).createSharedAccountPermissionsAsync({
|
|
77
|
+
ctx: { id: context.id },
|
|
78
|
+
sharedAccountCtx: { id: sharedAccountContext.id },
|
|
79
|
+
sharedAccount: { id: sharedAccountId },
|
|
80
|
+
groups: permissions.groups ?? [],
|
|
81
|
+
users: permissions.users?.map(user => ({ id: user.userdata.id })) ?? [],
|
|
82
|
+
capabilities: {
|
|
83
|
+
grantedCapability: permissions.capabilities?.grantedCapability ?? [],
|
|
84
|
+
deniedCapability: permissions.capabilities?.deniedCapability ?? [],
|
|
85
|
+
},
|
|
86
|
+
mailConfig: permissions.mailConfig,
|
|
87
|
+
calendarConfig: permissions.calendarConfig,
|
|
88
|
+
auth: context.admin
|
|
89
|
+
})
|
|
90
|
+
}
|
|
104
91
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
92
|
+
/**
|
|
93
|
+
* List all shared accounts in the specified context
|
|
94
|
+
*
|
|
95
|
+
* @param {Object} context - The context object whose shared account will be listed
|
|
96
|
+
* @param {*} pattern - An optional search pattern for the contexts
|
|
97
|
+
* @returns {Promise<Object[]>} The list of shared account objects
|
|
98
|
+
*/
|
|
99
|
+
async function list (context, pattern = '*') {
|
|
100
|
+
return (await getClient()).listAsync({
|
|
101
|
+
ctx: { id: context.id },
|
|
102
|
+
search_pattern: pattern,
|
|
103
|
+
auth: context.admin
|
|
104
|
+
})
|
|
105
|
+
}
|
|
119
106
|
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Get all data of a specified shared account in the specified context
|
|
109
|
+
*
|
|
110
|
+
* @param {*} context - The context where the shared account exists
|
|
111
|
+
* @param {number} sharedAccountId - ID of the requested shared account
|
|
112
|
+
* @returns {Promise<Object>} The shared account object
|
|
113
|
+
*/
|
|
114
|
+
async function getData (context, sharedAccountId) {
|
|
115
|
+
return (await getClient()).getDataAsync({
|
|
116
|
+
ctx: { id: context.id },
|
|
117
|
+
sharedAccount: { id: sharedAccountId },
|
|
118
|
+
auth: context.admin
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Remove a specified shared account in the specified context
|
|
124
|
+
*
|
|
125
|
+
* @param {*} context - The context object where the account will be removed
|
|
126
|
+
* @param {number} sharedAccountId - ID of the shared account to be removed
|
|
127
|
+
* @returns {Promise<void>}
|
|
128
|
+
*/
|
|
129
|
+
async function remove (context, sharedAccountId) {
|
|
130
|
+
return (await getClient()).deleteAsync({
|
|
131
|
+
ctx: { id: context.id },
|
|
132
|
+
sharedAccount: { id: sharedAccountId },
|
|
133
|
+
auth: context.admin
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { create, convertUserToSharedAccount, createSharedAccountPermissions, list, getData, remove }
|
|
133
138
|
}
|
|
139
|
+
|
|
140
|
+
// Classic module exports, bound to the env-configured default endpoint.
|
|
141
|
+
let defaultService
|
|
142
|
+
const service = () => (defaultService ??= createSharedAccountService(createClientAsync))
|
|
143
|
+
|
|
144
|
+
export const create = (...args) => service().create(...args)
|
|
145
|
+
export const convertUserToSharedAccount = (...args) => service().convertUserToSharedAccount(...args)
|
|
146
|
+
export const createSharedAccountPermissions = (...args) => service().createSharedAccountPermissions(...args)
|
|
147
|
+
export const list = (...args) => service().list(...args)
|
|
148
|
+
export const getData = (...args) => service().getData(...args)
|
|
149
|
+
export const remove = (...args) => service().remove(...args)
|
package/soap.js
CHANGED
|
@@ -40,12 +40,9 @@ const RETRY_OPTIONS = {
|
|
|
40
40
|
randomize: true // jitter to avoid thundering herd across parallel shards
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// This flag enables debug output including timing information.
|
|
48
|
-
const debug = process.env.DEBUG_SOAP === 'true'
|
|
43
|
+
// This flag enables debug output including timing information. Read lazily so
|
|
44
|
+
// importing the module has no environment-dependent behavior.
|
|
45
|
+
const debug = () => process.env.DEBUG_SOAP === 'true'
|
|
49
46
|
|
|
50
47
|
/**
|
|
51
48
|
* Walk one layer of common error-wrapper shapes:
|
|
@@ -91,44 +88,44 @@ function describeError (err) {
|
|
|
91
88
|
'unknown'
|
|
92
89
|
}
|
|
93
90
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
password: process.env.E2E_ADMIN_PW
|
|
113
|
-
}
|
|
91
|
+
// Swallow specifically timeout-related unhandled promise rejections as
|
|
92
|
+
// fallback; log everything else, as those indicate real problems. Installed
|
|
93
|
+
// on demand (idempotent) — importing the module must not touch process-global
|
|
94
|
+
// state. The env-configured default client installs it for backwards
|
|
95
|
+
// compatibility with the CLI and test-runner consumers; factory clients from
|
|
96
|
+
// createClientFactory never do, so embedding applications keep their own
|
|
97
|
+
// rejection policy.
|
|
98
|
+
let rejectionFilterInstalled = false
|
|
99
|
+
function installTimeoutRejectionFilter () {
|
|
100
|
+
if (rejectionFilterInstalled) return
|
|
101
|
+
rejectionFilterInstalled = true
|
|
102
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
103
|
+
const r = unwrapError(reason)
|
|
104
|
+
if (r?.code === 'ECONNABORTED' && (r?.message?.includes('timeout') || r?.message?.includes('exceeded'))) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
console.error('Unhandled promise rejection:', describeError(reason))
|
|
108
|
+
})
|
|
114
109
|
}
|
|
115
110
|
|
|
116
111
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* This is used for performance tracking of the SOAP methods.
|
|
112
|
+
* Lazily create the PerformanceObserver that logs SOAP method timings when
|
|
113
|
+
* DEBUG_SOAP is enabled. One observer per process, created on first client
|
|
114
|
+
* construction rather than at import time.
|
|
122
115
|
* @see https://nodejs.org/api/perf_hooks.html
|
|
123
|
-
* @params {PerformanceObserverEntryList} items The list of performance entries.
|
|
124
116
|
* @returns {void}
|
|
125
117
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
let debugObserverInstalled = false
|
|
119
|
+
function ensureDebugObserver () {
|
|
120
|
+
if (debugObserverInstalled || !debug()) return
|
|
121
|
+
debugObserverInstalled = true
|
|
122
|
+
const obs = new PerformanceObserver((items) => {
|
|
123
|
+
items.getEntries().forEach((entry) => {
|
|
124
|
+
console.log(`${entry.name} took ${Math.round(entry.duration)}ms`)
|
|
125
|
+
})
|
|
129
126
|
})
|
|
130
|
-
})
|
|
131
|
-
|
|
127
|
+
obs.observe({ entryTypes: ['measure'] })
|
|
128
|
+
}
|
|
132
129
|
|
|
133
130
|
async function logSoapError (e) {
|
|
134
131
|
console.error(describeError(e))
|
|
@@ -243,15 +240,109 @@ function handleFailedAttempt (context, label) {
|
|
|
243
240
|
}
|
|
244
241
|
|
|
245
242
|
/**
|
|
246
|
-
*
|
|
243
|
+
* @typedef {object} TransportOptions
|
|
244
|
+
* @property {string} url Base URL of the provisioning API (e.g. the core-mw
|
|
245
|
+
* admin Service), without the `/webservices` suffix.
|
|
246
|
+
* @property {{login: string, password: string}} auth Master admin credentials
|
|
247
|
+
* injected into every SOAP call unless the call passes its own `auth`.
|
|
248
|
+
*/
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Create a per-endpoint SOAP client constructor. Multiple factories coexist in
|
|
252
|
+
* one process, each bound to its own endpoint and credentials — nothing is
|
|
253
|
+
* read from the environment and no process-global state is touched.
|
|
254
|
+
* @param {TransportOptions} options
|
|
255
|
+
* @returns {(type: string) => Promise<Object>} createClientAsync bound to the endpoint.
|
|
256
|
+
*/
|
|
257
|
+
function createClientFactory ({ url, auth }) {
|
|
258
|
+
if (!url) throw new TypeError('createClientFactory: url is required')
|
|
259
|
+
if (!auth?.login || !auth?.password) throw new TypeError('createClientFactory: auth {login, password} is required')
|
|
260
|
+
const baseUrl = String(url).replace(/\/$/, '')
|
|
261
|
+
const defaultAuth = { auth: { login: auth.login, password: auth.password } }
|
|
262
|
+
return (type) => createTypedClientAsync(type, baseUrl, defaultAuth)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Memoize one SOAP client per service type, but only once it resolves.
|
|
267
|
+
*
|
|
268
|
+
* A plain `promise ??= createClient(type)` caches a rejection for the lifetime
|
|
269
|
+
* of the process: if the WSDL fetch exhausts its retry schedule or hits a
|
|
270
|
+
* non-retryable fault, every later call re-throws that stale error without ever
|
|
271
|
+
* touching the network again. That is fatal for a long-running embedder, where
|
|
272
|
+
* one bad startup would poison the service until the process restarts. Clearing
|
|
273
|
+
* the slot on failure keeps the happy path a single shared client while letting
|
|
274
|
+
* the next call re-attempt construction.
|
|
275
|
+
*
|
|
276
|
+
* @param {(type: string) => Promise<Object>} createClient
|
|
277
|
+
* @param {string} type The name of the service type.
|
|
278
|
+
* @returns {() => Promise<Object>} Getter returning the memoized client.
|
|
279
|
+
*/
|
|
280
|
+
function memoizeClient (createClient, type) {
|
|
281
|
+
let clientPromise
|
|
282
|
+
return () => (clientPromise ??= createClient(type).catch(e => {
|
|
283
|
+
clientPromise = undefined
|
|
284
|
+
throw e
|
|
285
|
+
}))
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// The env-configured default transport behind the classic module exports.
|
|
289
|
+
// Built lazily on first use: loads .env files, installs the timeout-rejection
|
|
290
|
+
// filter (CLI/test-runner compatibility), and binds PROVISIONING_URL +
|
|
291
|
+
// E2E_ADMIN_USER/PW. Factory transports never take this path.
|
|
292
|
+
let defaultCreateClient
|
|
293
|
+
function getDefaultCreateClient () {
|
|
294
|
+
if (!defaultCreateClient) {
|
|
295
|
+
for (const envFile of ['.env', '.env.defaults']) {
|
|
296
|
+
try { process.loadEnvFile(envFile) } catch {}
|
|
297
|
+
}
|
|
298
|
+
installTimeoutRejectionFilter()
|
|
299
|
+
// Report the missing *environment variables*, not createClientFactory's
|
|
300
|
+
// parameter names: a caller of the classic module exports never touched
|
|
301
|
+
// that function and would otherwise get an error naming an API it has
|
|
302
|
+
// never heard of. Note this is stricter than before — calls that carry
|
|
303
|
+
// their own `auth` (every user/account/reseller method takes
|
|
304
|
+
// `context.admin`) used to work with the master credentials unset, and
|
|
305
|
+
// now fail here. See the CHANGELOG entry for 0.2.0.
|
|
306
|
+
const missing = ['PROVISIONING_URL', 'E2E_ADMIN_USER', 'E2E_ADMIN_PW'].filter(key => !process.env[key])
|
|
307
|
+
if (missing.length) {
|
|
308
|
+
throw new TypeError(
|
|
309
|
+
`@open-xchange/soap-client: missing ${missing.join(', ')}. The module exports bind to the ` +
|
|
310
|
+
'environment; set these in .env, or use createProvisioningClient({ url, auth }) from ' +
|
|
311
|
+
"'@open-xchange/soap-client/client' to pass an endpoint and credentials explicitly."
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
defaultCreateClient = createClientFactory({
|
|
315
|
+
url: process.env.PROVISIONING_URL,
|
|
316
|
+
auth: { login: process.env.E2E_ADMIN_USER, password: process.env.E2E_ADMIN_PW }
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
return defaultCreateClient
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* This function creates a SOAP client for the specified service type, bound to
|
|
324
|
+
* the env-configured default endpoint (PROVISIONING_URL, E2E_ADMIN_USER/PW).
|
|
247
325
|
* @param {string} type The name of the service type.
|
|
248
326
|
* @returns {Promise<Object>} The SOAP client.
|
|
249
327
|
*/
|
|
250
328
|
async function createClientAsync (type) {
|
|
329
|
+
return getDefaultCreateClient()(type)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Create a SOAP client for the specified service type against a specific
|
|
334
|
+
* endpoint with specific credentials.
|
|
335
|
+
* @param {string} type The name of the service type.
|
|
336
|
+
* @param {string} baseUrl Base URL without trailing slash.
|
|
337
|
+
* @param {{auth: {login: string, password: string}}} defaultAuth Auth option injected per call.
|
|
338
|
+
* @returns {Promise<Object>} The SOAP client.
|
|
339
|
+
*/
|
|
340
|
+
async function createTypedClientAsync (type, baseUrl, defaultAuth) {
|
|
341
|
+
ensureDebugObserver()
|
|
251
342
|
const startMark = `${type}-start`
|
|
252
343
|
const endMark = `${type}-end`
|
|
253
344
|
performance.mark(startMark)
|
|
254
|
-
const endpoint = `${
|
|
345
|
+
const endpoint = `${baseUrl}/webservices/${type}`
|
|
255
346
|
const url = `${endpoint}/?wsdl`
|
|
256
347
|
// The WSDL fetch itself can hit transient network errors (ECONNRESET,
|
|
257
348
|
// ETIMEDOUT, ...). Without retry here, a single TLS hiccup during
|
|
@@ -284,11 +375,19 @@ async function createClientAsync (type) {
|
|
|
284
375
|
const startMark = `${prop}-start`
|
|
285
376
|
const endMark = `${prop}-end`
|
|
286
377
|
performance.mark(startMark)
|
|
287
|
-
|
|
288
|
-
|
|
378
|
+
// Pull `auth` out before merging. Spreading `options` wholesale made a
|
|
379
|
+
// present-but-undefined `auth` (every user/account/reseller method
|
|
380
|
+
// sends `auth: context.admin`, and a context without a resolved admin
|
|
381
|
+
// makes that undefined) overwrite the endpoint credentials, so the
|
|
382
|
+
// call went out unauthenticated even though valid ones were
|
|
383
|
+
// configured. An absent or undefined `auth` now falls back to the
|
|
384
|
+
// endpoint default; a real one still wins.
|
|
385
|
+
const { auth, ...rest } = options ?? {}
|
|
386
|
+
const soapOptions = { ...defaultAuth, ...rest }
|
|
387
|
+
if (auth) {
|
|
289
388
|
// only send login and password instead of complete admin object.
|
|
290
389
|
// this can fail because of ambiguous namespacing
|
|
291
|
-
soapOptions.auth = { login:
|
|
390
|
+
soapOptions.auth = { login: auth.login, password: auth.password }
|
|
292
391
|
}
|
|
293
392
|
|
|
294
393
|
try {
|
|
@@ -313,4 +412,4 @@ async function createClientAsync (type) {
|
|
|
313
412
|
})
|
|
314
413
|
}
|
|
315
414
|
|
|
316
|
-
export { createClientAsync, logSoapError, describeError, shouldAbortRetry, wrapSoapFault, unwrapError, handleFailedAttempt }
|
|
415
|
+
export { createClientAsync, createClientFactory, memoizeClient, installTimeoutRejectionFilter, logSoapError, describeError, shouldAbortRetry, wrapSoapFault, unwrapError, handleFailedAttempt }
|