@memberjunction/integration-connectors 5.51.0 → 6.1.0-edge.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.
Files changed (155) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +109 -277
  3. package/dist/index.d.ts +0 -36
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +7 -38
  6. package/dist/index.js.map +1 -1
  7. package/package.json +14 -19
  8. package/dist/AptifyConnector.d.ts +0 -95
  9. package/dist/AptifyConnector.d.ts.map +0 -1
  10. package/dist/AptifyConnector.js +0 -760
  11. package/dist/AptifyConnector.js.map +0 -1
  12. package/dist/BlackbaudConnector.d.ts +0 -64
  13. package/dist/BlackbaudConnector.d.ts.map +0 -1
  14. package/dist/BlackbaudConnector.js +0 -916
  15. package/dist/BlackbaudConnector.js.map +0 -1
  16. package/dist/ConstantContactConnector.d.ts +0 -137
  17. package/dist/ConstantContactConnector.d.ts.map +0 -1
  18. package/dist/ConstantContactConnector.js +0 -663
  19. package/dist/ConstantContactConnector.js.map +0 -1
  20. package/dist/CventConnector.d.ts +0 -146
  21. package/dist/CventConnector.d.ts.map +0 -1
  22. package/dist/CventConnector.js +0 -540
  23. package/dist/CventConnector.js.map +0 -1
  24. package/dist/DynamicsDataverseConnector.d.ts +0 -296
  25. package/dist/DynamicsDataverseConnector.d.ts.map +0 -1
  26. package/dist/DynamicsDataverseConnector.js +0 -790
  27. package/dist/DynamicsDataverseConnector.js.map +0 -1
  28. package/dist/FileFeedConnector.d.ts +0 -64
  29. package/dist/FileFeedConnector.d.ts.map +0 -1
  30. package/dist/FileFeedConnector.js +0 -190
  31. package/dist/FileFeedConnector.js.map +0 -1
  32. package/dist/FontevaConnector.d.ts +0 -153
  33. package/dist/FontevaConnector.d.ts.map +0 -1
  34. package/dist/FontevaConnector.js +0 -421
  35. package/dist/FontevaConnector.js.map +0 -1
  36. package/dist/GrowthZoneConnector.d.ts +0 -183
  37. package/dist/GrowthZoneConnector.d.ts.map +0 -1
  38. package/dist/GrowthZoneConnector.js +0 -702
  39. package/dist/GrowthZoneConnector.js.map +0 -1
  40. package/dist/HivebriteConnector.d.ts +0 -151
  41. package/dist/HivebriteConnector.d.ts.map +0 -1
  42. package/dist/HivebriteConnector.js +0 -533
  43. package/dist/HivebriteConnector.js.map +0 -1
  44. package/dist/HubSpotConnector.d.ts +0 -523
  45. package/dist/HubSpotConnector.d.ts.map +0 -1
  46. package/dist/HubSpotConnector.js +0 -3110
  47. package/dist/HubSpotConnector.js.map +0 -1
  48. package/dist/IMISConnector.d.ts +0 -171
  49. package/dist/IMISConnector.d.ts.map +0 -1
  50. package/dist/IMISConnector.js +0 -695
  51. package/dist/IMISConnector.js.map +0 -1
  52. package/dist/MJToMJConnector.d.ts +0 -279
  53. package/dist/MJToMJConnector.d.ts.map +0 -1
  54. package/dist/MJToMJConnector.js +0 -893
  55. package/dist/MJToMJConnector.js.map +0 -1
  56. package/dist/MagnetMailConnector.d.ts +0 -163
  57. package/dist/MagnetMailConnector.d.ts.map +0 -1
  58. package/dist/MagnetMailConnector.js +0 -823
  59. package/dist/MagnetMailConnector.js.map +0 -1
  60. package/dist/MailchimpConnector.d.ts +0 -197
  61. package/dist/MailchimpConnector.d.ts.map +0 -1
  62. package/dist/MailchimpConnector.js +0 -791
  63. package/dist/MailchimpConnector.js.map +0 -1
  64. package/dist/MemberSuiteConnector.d.ts +0 -185
  65. package/dist/MemberSuiteConnector.d.ts.map +0 -1
  66. package/dist/MemberSuiteConnector.js +0 -762
  67. package/dist/MemberSuiteConnector.js.map +0 -1
  68. package/dist/NeonCRMConnector.d.ts +0 -273
  69. package/dist/NeonCRMConnector.d.ts.map +0 -1
  70. package/dist/NeonCRMConnector.js +0 -1039
  71. package/dist/NeonCRMConnector.js.map +0 -1
  72. package/dist/NetForumConnector.d.ts +0 -170
  73. package/dist/NetForumConnector.d.ts.map +0 -1
  74. package/dist/NetForumConnector.js +0 -778
  75. package/dist/NetForumConnector.js.map +0 -1
  76. package/dist/NetSuiteConnector.d.ts +0 -136
  77. package/dist/NetSuiteConnector.d.ts.map +0 -1
  78. package/dist/NetSuiteConnector.js +0 -703
  79. package/dist/NetSuiteConnector.js.map +0 -1
  80. package/dist/NimbleAMSConnector.d.ts +0 -142
  81. package/dist/NimbleAMSConnector.d.ts.map +0 -1
  82. package/dist/NimbleAMSConnector.js +0 -703
  83. package/dist/NimbleAMSConnector.js.map +0 -1
  84. package/dist/NoviConnector.d.ts +0 -178
  85. package/dist/NoviConnector.d.ts.map +0 -1
  86. package/dist/NoviConnector.js +0 -566
  87. package/dist/NoviConnector.js.map +0 -1
  88. package/dist/ORCIDConnector.d.ts +0 -207
  89. package/dist/ORCIDConnector.d.ts.map +0 -1
  90. package/dist/ORCIDConnector.js +0 -708
  91. package/dist/ORCIDConnector.js.map +0 -1
  92. package/dist/OpenWaterConnector.d.ts +0 -173
  93. package/dist/OpenWaterConnector.d.ts.map +0 -1
  94. package/dist/OpenWaterConnector.js +0 -893
  95. package/dist/OpenWaterConnector.js.map +0 -1
  96. package/dist/PathLMSConnector.d.ts +0 -395
  97. package/dist/PathLMSConnector.d.ts.map +0 -1
  98. package/dist/PathLMSConnector.js +0 -1281
  99. package/dist/PathLMSConnector.js.map +0 -1
  100. package/dist/PheedLoopConnector.d.ts +0 -119
  101. package/dist/PheedLoopConnector.d.ts.map +0 -1
  102. package/dist/PheedLoopConnector.js +0 -429
  103. package/dist/PheedLoopConnector.js.map +0 -1
  104. package/dist/PropFuelConnector.d.ts +0 -153
  105. package/dist/PropFuelConnector.d.ts.map +0 -1
  106. package/dist/PropFuelConnector.js +0 -450
  107. package/dist/PropFuelConnector.js.map +0 -1
  108. package/dist/QuickBooksConnector.d.ts +0 -92
  109. package/dist/QuickBooksConnector.d.ts.map +0 -1
  110. package/dist/QuickBooksConnector.js +0 -713
  111. package/dist/QuickBooksConnector.js.map +0 -1
  112. package/dist/RasaConnector.d.ts +0 -152
  113. package/dist/RasaConnector.d.ts.map +0 -1
  114. package/dist/RasaConnector.js +0 -929
  115. package/dist/RasaConnector.js.map +0 -1
  116. package/dist/Reach360Connector.d.ts +0 -175
  117. package/dist/Reach360Connector.d.ts.map +0 -1
  118. package/dist/Reach360Connector.js +0 -796
  119. package/dist/Reach360Connector.js.map +0 -1
  120. package/dist/RelationalDBConnector.d.ts +0 -108
  121. package/dist/RelationalDBConnector.d.ts.map +0 -1
  122. package/dist/RelationalDBConnector.js +0 -228
  123. package/dist/RelationalDBConnector.js.map +0 -1
  124. package/dist/RhythmConnector.d.ts +0 -188
  125. package/dist/RhythmConnector.d.ts.map +0 -1
  126. package/dist/RhythmConnector.js +0 -784
  127. package/dist/RhythmConnector.js.map +0 -1
  128. package/dist/SageIntacctConnector.d.ts +0 -337
  129. package/dist/SageIntacctConnector.d.ts.map +0 -1
  130. package/dist/SageIntacctConnector.js +0 -2081
  131. package/dist/SageIntacctConnector.js.map +0 -1
  132. package/dist/SalesforceConnector.d.ts +0 -461
  133. package/dist/SalesforceConnector.d.ts.map +0 -1
  134. package/dist/SalesforceConnector.js +0 -1862
  135. package/dist/SalesforceConnector.js.map +0 -1
  136. package/dist/SharePointConnector.d.ts +0 -188
  137. package/dist/SharePointConnector.d.ts.map +0 -1
  138. package/dist/SharePointConnector.js +0 -716
  139. package/dist/SharePointConnector.js.map +0 -1
  140. package/dist/WicketConnector.d.ts +0 -252
  141. package/dist/WicketConnector.d.ts.map +0 -1
  142. package/dist/WicketConnector.js +0 -938
  143. package/dist/WicketConnector.js.map +0 -1
  144. package/dist/WildApricotConnector.d.ts +0 -201
  145. package/dist/WildApricotConnector.d.ts.map +0 -1
  146. package/dist/WildApricotConnector.js +0 -1129
  147. package/dist/WildApricotConnector.js.map +0 -1
  148. package/dist/YourMembershipConnector.d.ts +0 -349
  149. package/dist/YourMembershipConnector.d.ts.map +0 -1
  150. package/dist/YourMembershipConnector.js +0 -4062
  151. package/dist/YourMembershipConnector.js.map +0 -1
  152. package/dist/generate-integration-actions.d.ts +0 -23
  153. package/dist/generate-integration-actions.d.ts.map +0 -1
  154. package/dist/generate-integration-actions.js +0 -201
  155. package/dist/generate-integration-actions.js.map +0 -1
@@ -1,823 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- /**
8
- * ⚠️ NEEDS T10 LIVE VERIFICATION BEFORE PRODUCTION ⚠️
9
- *
10
- * 2026-05-20 vendor-truth audit (vs public WSDL at hlma-apie1.magnetmail.net/mmapi.asmx?WSDL)
11
- * found 8 wire-level mismatches between this connector and the vendor's actual API
12
- * surface. **All 8 are now fixed** based on WSDL evidence. T10 sandbox verification
13
- * is still needed before customer rollout.
14
- *
15
- * Fixed in this round:
16
- * ✓ SOAP namespace: changed 'http://api.magnetmail.net/' → 'http://www.magnetmail.net/'
17
- * ✓ Auth placement: moved from operation body to <mmAuthHeader> SOAP HEADER
18
- * ✓ Authenticate field: 'user_id' → 'username'
19
- * ✓ Authenticate response: now reads <sessionId> (with capital S/I) as primary
20
- * ✓ Pagination: 'start_row'/'row_count' → 'pageNumber'/'pageCount' with
21
- * SupportsPagination=false on operations that take no pagination
22
- * (searchForRecipients, getMessagesUTC)
23
- * ✓ getMessagesUTC watermark params in metadata: 'send_date_*' →
24
- * 'sentStartDate'/'sentEndDate'
25
- * ✓ addRecipient Groups[] complex array: declared via Recipients DQP hint
26
- * ✓ searchForRecipients <criteria> wrapper: envelope-builder branches via the
27
- * COMPLEX_WRAPPERS map so the filter args nest inside <criteria> for this
28
- * specific operation. Add entries to that map for any future operation the
29
- * WSDL wraps similarly.
30
- *
31
- * Until T10 (live tenant) confirms this connector now exchanges successful calls,
32
- * keep customer-facing rollout gated.
33
- */
34
- import { RegisterClass } from '@memberjunction/global';
35
- import { Metadata } from '@memberjunction/core';
36
- import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
37
- import { BaseIntegrationConnector, } from '@memberjunction/integration-engine';
38
- // ─── Constants ───────────────────────────────────────────────────────
39
- /** Default MagnetMail SOAP endpoint (shared hostname, not per-customer). */
40
- const DEFAULT_ENDPOINT = 'https://hlma-apie1.magnetmail.net/mmapi.asmx';
41
- /** Default MagnetMail SOAP namespace (derived from the public WSDL's tns). */
42
- const DEFAULT_NAMESPACE = 'http://www.magnetmail.net/';
43
- /** Default request timeout for MagnetMail (slower than typical REST). */
44
- const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
45
- /** Default minimum milliseconds between API calls. */
46
- const DEFAULT_MIN_REQUEST_INTERVAL_MS = 500;
47
- /** Default retry count. */
48
- const DEFAULT_MAX_RETRIES = 4;
49
- /** Default session TTL — re-authenticate after 30 minutes of use. */
50
- const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000;
51
- /** Keys in DefaultQueryParams JSON that are reserved for connector behavior, not SOAP args. */
52
- const RESERVED_META_KEYS = new Set([
53
- 'create_action', 'update_action', 'delete_action', 'detail_action',
54
- 'count_action', 'run_action', 'enhanced_action',
55
- 'beta_create_action', 'beta_status_action',
56
- 'watermark_field', 'watermark_from_param', 'watermark_to_param',
57
- ]);
58
- // ─── MagnetMail Static Object Metadata for Action Generation ─────────
59
- const MAGNETMAIL_OBJECTS = [
60
- {
61
- Name: 'Recipients',
62
- DisplayName: 'Recipient',
63
- Description: 'An email recipient / contact record in MagnetMail.',
64
- SupportsWrite: true,
65
- Fields: [
66
- { Name: 'recipient_id', DisplayName: 'Recipient ID', Type: 'number', IsRequired: true, IsReadOnly: true, IsPrimaryKey: true, Description: 'MagnetMail recipient identifier (integer).' },
67
- { Name: 'email', DisplayName: 'Email', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Primary email address (de-facto natural key).' },
68
- { Name: 'first_name', DisplayName: 'First Name', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'First name.' },
69
- { Name: 'last_name', DisplayName: 'Last Name', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Last name.' },
70
- { Name: 'company', DisplayName: 'Company', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Company name.' },
71
- { Name: 'phone', DisplayName: 'Phone', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Phone number.' },
72
- { Name: 'unsubscribed', DisplayName: 'Unsubscribed', Type: 'boolean', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'True when the recipient has unsubscribed.' },
73
- ],
74
- },
75
- {
76
- Name: 'Groups',
77
- DisplayName: 'Group',
78
- Description: 'A distribution group in MagnetMail.',
79
- SupportsWrite: true,
80
- Fields: [
81
- { Name: 'group_id', DisplayName: 'Group ID', Type: 'number', IsRequired: true, IsReadOnly: true, IsPrimaryKey: true, Description: 'Group identifier.' },
82
- { Name: 'group_name', DisplayName: 'Group Name', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Group name.' },
83
- { Name: 'description', DisplayName: 'Description', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Group description.' },
84
- { Name: 'category_id', DisplayName: 'Category ID', Type: 'number', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Associated category.' },
85
- ],
86
- },
87
- {
88
- Name: 'Messages',
89
- DisplayName: 'Message',
90
- Description: 'An email message / template in MagnetMail.',
91
- SupportsWrite: true,
92
- Fields: [
93
- { Name: 'message_id', DisplayName: 'Message ID', Type: 'number', IsRequired: true, IsReadOnly: true, IsPrimaryKey: true, Description: 'Message identifier.' },
94
- { Name: 'message_name', DisplayName: 'Message Name', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Message name.' },
95
- { Name: 'subject', DisplayName: 'Subject', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Email subject.' },
96
- { Name: 'from_email', DisplayName: 'From Email', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Sender email address.' },
97
- { Name: 'createDate', DisplayName: 'Create Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Message creation timestamp.' },
98
- { Name: 'lastSent', DisplayName: 'Last Sent', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Most recent send timestamp.' },
99
- ],
100
- },
101
- {
102
- Name: 'UploadJobs',
103
- DisplayName: 'Upload Job',
104
- Description: 'An async bulk CSV upload job in MagnetMail.',
105
- SupportsWrite: true,
106
- Fields: [
107
- { Name: 'jobid', DisplayName: 'Job ID', Type: 'number', IsRequired: true, IsReadOnly: true, IsPrimaryKey: true, Description: 'Upload job identifier.' },
108
- { Name: 'status', DisplayName: 'Status', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Job status.' },
109
- { Name: 'total_rows', DisplayName: 'Total Rows', Type: 'number', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Total rows in CSV.' },
110
- { Name: 'processed_rows', DisplayName: 'Processed Rows', Type: 'number', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Number of rows processed.' },
111
- ],
112
- },
113
- ];
114
- // ─── Connector Implementation ────────────────────────────────────────
115
- /**
116
- * Connector for Higher Logic MagnetMail (mmapi.asmx SOAP API).
117
- *
118
- * Extends BaseIntegrationConnector directly because MagnetMail is SOAP/XML.
119
- * The generic BaseRESTIntegrationConnector pagination helpers don't apply;
120
- * MagnetMail has per-operation offset/row_count pagination, UTC-variant names,
121
- * and envelope-wrapped responses that need XML parsing.
122
- *
123
- * Operations are driven by `IntegrationObject` metadata:
124
- * - `APIPath` holds the SOAP action name for list/read (e.g., `getMessagesUTC`)
125
- * - `ResponseDataKey` holds the response element wrapping the result (e.g., `getMessagesUTCResult`)
126
- * - `DefaultQueryParams` carries additional metadata:
127
- * - `create_action` / `update_action` / `delete_action` / `detail_action` — SOAP action names
128
- * - `watermark_field` / `watermark_from_param` / `watermark_to_param` — incremental sync settings
129
- *
130
- * Auth flow:
131
- * 1. POST `Authenticate` with userId + password -> session token
132
- * 2. Attach (user_id, session) to every subsequent SOAP call
133
- * 3. Cache session for SESSION_TTL_MS; refresh on expiry or auth failure
134
- */
135
- let MagnetMailConnector = class MagnetMailConnector extends BaseIntegrationConnector {
136
- constructor() {
137
- // ── State ────────────────────────────────────────────────────────
138
- super(...arguments);
139
- this.cachedSession = null;
140
- this.lastRequestTime = 0;
141
- }
142
- // ── Capability Getters ───────────────────────────────────────────
143
- get SupportsCreate() { return true; }
144
- get SupportsUpdate() { return true; }
145
- get SupportsDelete() { return true; }
146
- get SupportsSearch() { return false; }
147
- get SupportsListing() { return false; }
148
- get IntegrationName() { return 'MagnetMail'; }
149
- // ── Action Generation ────────────────────────────────────────────
150
- GetIntegrationObjects() {
151
- return MAGNETMAIL_OBJECTS;
152
- }
153
- GetActionGeneratorConfig() {
154
- const objects = this.GetIntegrationObjects();
155
- if (objects.length === 0)
156
- return null;
157
- return {
158
- IntegrationName: 'MagnetMail',
159
- CategoryName: 'MagnetMail',
160
- IconClass: 'fa-solid fa-envelope-circle-check',
161
- Objects: objects,
162
- IncludeSearch: false,
163
- IncludeList: false,
164
- CategoryDescription: 'Higher Logic MagnetMail email marketing integration actions',
165
- ParentCategoryName: 'Business Apps',
166
- };
167
- }
168
- // ── Default Configuration ────────────────────────────────────────
169
- GetDefaultConfiguration() {
170
- return {
171
- DefaultSchemaName: 'MagnetMail',
172
- DefaultObjects: [
173
- {
174
- SourceObjectName: 'Recipients',
175
- TargetTableName: 'MagnetMail_Recipient',
176
- TargetEntityName: 'MagnetMail Recipients',
177
- SyncEnabled: true,
178
- FieldMappings: this.GetDefaultFieldMappings('Recipients', 'Contacts'),
179
- },
180
- {
181
- SourceObjectName: 'Groups',
182
- TargetTableName: 'MagnetMail_Group',
183
- TargetEntityName: 'MagnetMail Groups',
184
- SyncEnabled: true,
185
- FieldMappings: [],
186
- },
187
- {
188
- SourceObjectName: 'Messages',
189
- TargetTableName: 'MagnetMail_Message',
190
- TargetEntityName: 'MagnetMail Messages',
191
- SyncEnabled: true,
192
- FieldMappings: [],
193
- },
194
- ],
195
- };
196
- }
197
- GetDefaultFieldMappings(objectName, _entityName) {
198
- if (objectName === 'Recipients') {
199
- return [
200
- { SourceFieldName: 'recipient_id', DestinationFieldName: 'ExternalID', IsKeyField: true },
201
- { SourceFieldName: 'email', DestinationFieldName: 'Email' },
202
- { SourceFieldName: 'first_name', DestinationFieldName: 'FirstName' },
203
- { SourceFieldName: 'last_name', DestinationFieldName: 'LastName' },
204
- { SourceFieldName: 'phone', DestinationFieldName: 'Phone' },
205
- { SourceFieldName: 'company', DestinationFieldName: 'Company' },
206
- ];
207
- }
208
- return [];
209
- }
210
- // ── TestConnection ───────────────────────────────────────────────
211
- /**
212
- * Exercises the Authenticate operation and then calls getUserDetails to
213
- * validate the session token is accepted by a regular endpoint.
214
- */
215
- async TestConnection(companyIntegration, contextUser) {
216
- try {
217
- const session = await this.GetSession(companyIntegration, contextUser, true);
218
- const response = await this.InvokeSoapOperation(session, 'getUserDetails', {});
219
- const account = this.ExtractNodeText(response, 'account_name') ?? 'authenticated';
220
- return {
221
- Success: true,
222
- Message: `Successfully authenticated to MagnetMail (user_id=${session.Config.UserId}, account=${account})`,
223
- ServerVersion: 'MagnetMail mmapi.asmx (SOAP 1.1/1.2)',
224
- };
225
- }
226
- catch (err) {
227
- const message = err instanceof Error ? err.message : String(err);
228
- return { Success: false, Message: `Connection failed: ${message}` };
229
- }
230
- }
231
- // ── Discovery ────────────────────────────────────────────────────
232
- /**
233
- * Returns static metadata — MagnetMail's WSDL is fixed and no runtime
234
- * discovery is possible for SOAP operations. Engine's cached IntegrationObjects
235
- * are returned when available; otherwise the connector's static list.
236
- */
237
- async DiscoverObjects(companyIntegration, _contextUser) {
238
- const cached = IntegrationEngineBase.Instance.GetActiveIntegrationObjects(companyIntegration.IntegrationID);
239
- if (cached && cached.length > 0) {
240
- return cached.map(obj => ({
241
- ID: obj.ID,
242
- Name: obj.Name,
243
- Label: obj.DisplayName ?? obj.Name,
244
- Description: obj.Description ?? undefined,
245
- SupportsIncrementalSync: obj.SupportsIncrementalSync,
246
- SupportsWrite: obj.SupportsWrite,
247
- }));
248
- }
249
- return MAGNETMAIL_OBJECTS.map(o => ({
250
- Name: o.Name,
251
- Label: o.DisplayName,
252
- Description: o.Description,
253
- SupportsIncrementalSync: o.Name === 'Messages' || o.Name === 'Unsubscribes' || o.Name === 'MessageTrackingDetailed',
254
- SupportsWrite: o.SupportsWrite,
255
- }));
256
- }
257
- async DiscoverFields(companyIntegration, objectName, _contextUser) {
258
- const cached = IntegrationEngineBase.Instance.GetIntegrationObject(companyIntegration.IntegrationID, objectName);
259
- if (cached) {
260
- const fields = IntegrationEngineBase.Instance.GetIntegrationObjectFields(cached.ID);
261
- return fields.map(f => ({
262
- Name: f.Name,
263
- Label: f.DisplayName ?? f.Name,
264
- Description: f.Description ?? undefined,
265
- DataType: f.Type,
266
- IsRequired: f.IsRequired,
267
- IsUniqueKey: f.IsUniqueKey || f.IsPrimaryKey,
268
- IsReadOnly: f.IsReadOnly,
269
- IsForeignKey: f.RelatedIntegrationObjectID != null,
270
- ForeignKeyTarget: f.RelatedIntegrationObject ?? null,
271
- }));
272
- }
273
- const obj = MAGNETMAIL_OBJECTS.find(o => o.Name === objectName);
274
- if (!obj)
275
- return [];
276
- return obj.Fields.map(f => ({
277
- Name: f.Name,
278
- Label: f.DisplayName,
279
- Description: f.Description,
280
- DataType: f.Type,
281
- IsRequired: f.IsRequired,
282
- IsUniqueKey: f.IsPrimaryKey,
283
- IsReadOnly: f.IsReadOnly,
284
- }));
285
- }
286
- // ── FetchChanges ─────────────────────────────────────────────────
287
- /**
288
- * Drives a list/read SOAP operation using the object's metadata. Pagination
289
- * uses MagnetMail's `start_row` / `row_count` convention; incremental sync
290
- * passes watermark_from/to parameters when configured on the IntegrationObject.
291
- */
292
- async FetchChanges(ctx) {
293
- const obj = this.ResolveObject(ctx.CompanyIntegration.IntegrationID, ctx.ObjectName);
294
- const meta = this.BuildObjectMeta(obj);
295
- const session = await this.GetSession(ctx.CompanyIntegration, ctx.ContextUser);
296
- const offset = ctx.CurrentOffset ?? 0;
297
- const pageSize = ctx.BatchSize || obj.DefaultPageSize || 500;
298
- const args = this.BuildFetchArgs(meta, ctx, offset, pageSize);
299
- const responseXml = await this.InvokeSoapOperation(session, meta.ListAction, args);
300
- const records = this.ParseRecordArray(responseXml, meta.ResultPath);
301
- const pkField = this.GetPrimaryKeyField(obj.ID);
302
- const externalRecords = records.map(r => this.RawToExternalRecord(r, ctx.ObjectName, pkField));
303
- const watermarkAfter = meta.WatermarkField ? this.ComputeWatermark(records, meta.WatermarkField) : undefined;
304
- return {
305
- Records: externalRecords,
306
- HasMore: obj.SupportsPagination && records.length >= pageSize,
307
- NewWatermarkValue: watermarkAfter,
308
- NextOffset: records.length >= pageSize ? offset + records.length : undefined,
309
- };
310
- }
311
- // ── CRUD operations ──────────────────────────────────────────────
312
- async CreateRecord(ctx) {
313
- return this.ExecuteMutation(ctx, 'CreateAction', ctx.Attributes, null);
314
- }
315
- async UpdateRecord(ctx) {
316
- return this.ExecuteMutation(ctx, 'UpdateAction', ctx.Attributes, ctx.ExternalID);
317
- }
318
- async DeleteRecord(ctx) {
319
- return this.ExecuteMutation(ctx, 'DeleteAction', {}, ctx.ExternalID);
320
- }
321
- async GetRecord(ctx) {
322
- const companyIntegration = ctx.CompanyIntegration;
323
- const contextUser = ctx.ContextUser;
324
- const obj = this.ResolveObject(companyIntegration.IntegrationID, ctx.ObjectName);
325
- const meta = this.BuildObjectMeta(obj);
326
- const action = meta.DetailAction;
327
- if (!action)
328
- return null;
329
- const session = await this.GetSession(companyIntegration, contextUser);
330
- const pkField = this.GetPrimaryKeyField(obj.ID);
331
- const args = { ...meta.ExtraArgs, [pkField]: ctx.ExternalID };
332
- const responseXml = await this.InvokeSoapOperation(session, action, args);
333
- const records = this.ParseRecordArray(responseXml, `${action}Result`);
334
- if (records.length === 0)
335
- return null;
336
- return this.RawToExternalRecord(records[0], ctx.ObjectName, pkField);
337
- }
338
- // ── Mutation helper ──────────────────────────────────────────────
339
- /**
340
- * Shared create/update/delete implementation. Reads the mutation SOAP action
341
- * name from the IntegrationObject's DefaultQueryParams metadata and POSTs
342
- * the given attributes.
343
- */
344
- async ExecuteMutation(ctx, metaKey, attributes, externalID) {
345
- const companyIntegration = ctx.CompanyIntegration;
346
- const contextUser = ctx.ContextUser;
347
- const obj = this.ResolveObject(companyIntegration.IntegrationID, ctx.ObjectName);
348
- const meta = this.BuildObjectMeta(obj);
349
- const actionName = this.GetMutationActionName(meta, metaKey);
350
- if (!actionName) {
351
- return {
352
- Success: false,
353
- ErrorMessage: `${metaKey} is not supported for MagnetMail object ${ctx.ObjectName}`,
354
- StatusCode: 400,
355
- };
356
- }
357
- try {
358
- const session = await this.GetSession(companyIntegration, contextUser);
359
- const args = this.BuildMutationArgs(meta, attributes, externalID, obj);
360
- const responseXml = await this.InvokeSoapOperation(session, actionName, args);
361
- const extractedId = this.ExtractMutationResultId(responseXml, actionName, obj);
362
- // CREATE-ONLY: a successful SOAP create that yields no record ID is a silent
363
- // record-loss bug (duplicate creates on the next sync). Fail loudly via the base
364
- // helper. Update/Delete keep their existing semantics (they echo the passed-in ID).
365
- if (metaKey === 'CreateAction') {
366
- return this.BuildCreatedResult(extractedId, 200, ctx.ObjectName);
367
- }
368
- const returnedId = extractedId ?? externalID ?? '';
369
- return { Success: true, ExternalID: returnedId, StatusCode: 200 };
370
- }
371
- catch (err) {
372
- return this.BuildCRUDError(err, metaKey, ctx.ObjectName);
373
- }
374
- }
375
- GetMutationActionName(meta, key) {
376
- if (key === 'CreateAction')
377
- return meta.CreateAction;
378
- if (key === 'UpdateAction')
379
- return meta.UpdateAction;
380
- return meta.DeleteAction;
381
- }
382
- BuildMutationArgs(meta, attributes, externalID, obj) {
383
- const args = { ...meta.ExtraArgs };
384
- for (const [key, value] of Object.entries(attributes)) {
385
- if (value == null)
386
- continue;
387
- args[key] = String(value);
388
- }
389
- if (externalID != null) {
390
- const pkField = this.GetPrimaryKeyField(obj.ID);
391
- args[pkField] = externalID;
392
- }
393
- return args;
394
- }
395
- /**
396
- * Attempts to extract an ID from a create/update response. MagnetMail
397
- * conventions vary per operation; this falls back to returning the
398
- * external ID already passed in.
399
- */
400
- ExtractMutationResultId(responseXml, action, obj) {
401
- const pkField = this.GetPrimaryKeyField(obj.ID);
402
- const direct = this.ExtractNodeText(responseXml, pkField);
403
- if (direct)
404
- return direct;
405
- const resultPath = `${action}Result`;
406
- const wrapped = this.ExtractWrappedNodeText(responseXml, resultPath, pkField);
407
- if (wrapped)
408
- return wrapped;
409
- return null;
410
- }
411
- // ── Session Management ───────────────────────────────────────────
412
- async GetSession(companyIntegration, contextUser, forceRefresh = false) {
413
- if (!forceRefresh && this.cachedSession && this.IsSessionValid()) {
414
- return this.cachedSession;
415
- }
416
- const config = await this.ParseConfig(companyIntegration, contextUser);
417
- const token = await this.Authenticate(config);
418
- const session = {
419
- SessionToken: token,
420
- CreatedAt: Date.now(),
421
- Config: config,
422
- };
423
- this.cachedSession = session;
424
- return session;
425
- }
426
- IsSessionValid() {
427
- if (!this.cachedSession)
428
- return false;
429
- const ttl = this.cachedSession.Config.SessionTTLMs ?? DEFAULT_SESSION_TTL_MS;
430
- return Date.now() - this.cachedSession.CreatedAt < ttl;
431
- }
432
- async Authenticate(config) {
433
- // Vendor WSDL: Authenticate accepts {username, password} (NOT user_id).
434
- const envelope = this.BuildEnvelope(config, 'Authenticate', {
435
- username: config.UserId,
436
- password: config.Password,
437
- }, /* includeSession */ false);
438
- const responseXml = await this.PostSOAP(config, 'Authenticate', envelope);
439
- // Vendor returns <sessionId> (capital S, capital I). Try AuthenticateResult
440
- // first (some operations wrap in an *Result envelope), then sessionId, and
441
- // fall back to lowercase variants for older/forked deployments.
442
- const token = this.ExtractNodeText(responseXml, 'AuthenticateResult')
443
- ?? this.ExtractNodeText(responseXml, 'sessionId')
444
- ?? this.ExtractNodeText(responseXml, 'session')
445
- ?? this.ExtractNodeText(responseXml, 'sessionid');
446
- if (!token) {
447
- throw new Error('MagnetMailConnector: Authenticate did not return a session token');
448
- }
449
- return token;
450
- }
451
- // ── Configuration Parsing ────────────────────────────────────────
452
- async ParseConfig(companyIntegration, contextUser) {
453
- const credentialID = companyIntegration.CredentialID;
454
- if (credentialID) {
455
- const config = await this.LoadFromCredentialEntity(credentialID, contextUser);
456
- if (config)
457
- return config;
458
- }
459
- const configJson = companyIntegration.Configuration;
460
- if (configJson) {
461
- const parsed = JSON.parse(configJson);
462
- return this.ValidateConfig(parsed);
463
- }
464
- throw new Error('MagnetMailConnector: No credentials or configuration found on CompanyIntegration');
465
- }
466
- async LoadFromCredentialEntity(credentialID, contextUser, provider) {
467
- const md = provider ?? new Metadata();
468
- const credential = await md.GetEntityObject('MJ: Credentials', contextUser);
469
- const loaded = await credential.Load(credentialID);
470
- if (!loaded || !credential.Values)
471
- return null;
472
- try {
473
- const raw = JSON.parse(credential.Values);
474
- const parsed = {
475
- UserId: raw.userId ?? raw.UserId,
476
- Password: raw.password ?? raw.Password,
477
- Endpoint: raw.endpoint ?? raw.Endpoint,
478
- Namespace: raw.namespace ?? raw.Namespace,
479
- };
480
- return this.ValidateConfig(parsed);
481
- }
482
- catch {
483
- return null;
484
- }
485
- }
486
- ValidateConfig(raw) {
487
- if (!raw.UserId)
488
- throw new Error('MagnetMailConnector: UserId is required');
489
- if (!raw.Password)
490
- throw new Error('MagnetMailConnector: Password is required');
491
- return {
492
- UserId: raw.UserId,
493
- Password: raw.Password,
494
- Endpoint: raw.Endpoint ?? DEFAULT_ENDPOINT,
495
- Namespace: raw.Namespace ?? DEFAULT_NAMESPACE,
496
- MaxRetries: raw.MaxRetries ?? DEFAULT_MAX_RETRIES,
497
- RequestTimeoutMs: raw.RequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
498
- MinRequestIntervalMs: raw.MinRequestIntervalMs ?? DEFAULT_MIN_REQUEST_INTERVAL_MS,
499
- SessionTTLMs: raw.SessionTTLMs ?? DEFAULT_SESSION_TTL_MS,
500
- };
501
- }
502
- // ── SOAP helpers ─────────────────────────────────────────────────
503
- /**
504
- * Invokes a SOAP operation with session authentication. The `args` map is
505
- * serialised as child elements of the operation element; complex types
506
- * should be pre-serialised to string by callers.
507
- */
508
- async InvokeSoapOperation(session, action, args) {
509
- const envelope = this.BuildEnvelope(session.Config, action, args, /* includeSession */ true, session.SessionToken);
510
- return this.PostSOAP(session.Config, action, envelope);
511
- }
512
- /**
513
- * Builds a SOAP 1.1 envelope. Vendor WSDL places authentication in a SOAP
514
- * HEADER element <mmAuthHeader> containing <sessionId> + <user_id> — NOT in
515
- * the operation body. The operation body carries only operation-specific args.
516
- */
517
- BuildEnvelope(config, operation, args, includeSession, sessionToken) {
518
- const namespace = config.Namespace ?? DEFAULT_NAMESPACE;
519
- const headerBlock = includeSession
520
- ? ` <soap:Header>\n <mmAuthHeader xmlns="${namespace}">\n <sessionId>${this.EscapeXmlValue(sessionToken ?? '')}</sessionId>\n <user_id>${this.EscapeXmlValue(config.UserId)}</user_id>\n </mmAuthHeader>\n </soap:Header>\n`
521
- : '';
522
- // Operations whose WSDL wraps all arguments in a single complex element.
523
- // searchForRecipients is the canonical one: all filter fields go INSIDE
524
- // a <criteria> element rather than as flat children of the operation.
525
- const COMPLEX_WRAPPERS = {
526
- 'searchForRecipients': 'criteria',
527
- };
528
- const wrapper = COMPLEX_WRAPPERS[operation];
529
- const renderArgs = (entries) => entries.map(([k, v]) => `<${k}>${this.EscapeXmlValue(v)}</${k}>`).join('\n ');
530
- let body;
531
- if (wrapper) {
532
- const inner = renderArgs(Object.entries(args));
533
- body = ` <${wrapper}>\n ${inner}\n </${wrapper}>`;
534
- }
535
- else {
536
- body = ` ${renderArgs(Object.entries(args))}`;
537
- }
538
- return `<?xml version="1.0" encoding="utf-8"?>
539
- <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
540
- ${headerBlock} <soap:Body>
541
- <${operation} xmlns="${namespace}">
542
- ${body}
543
- </${operation}>
544
- </soap:Body>
545
- </soap:Envelope>`;
546
- }
547
- /**
548
- * POSTs a SOAP envelope with throttling, retry, and timeout.
549
- */
550
- async PostSOAP(config, action, envelope) {
551
- const endpoint = config.Endpoint ?? DEFAULT_ENDPOINT;
552
- const namespace = config.Namespace ?? DEFAULT_NAMESPACE;
553
- const soapAction = `${namespace}${action}`;
554
- const maxRetries = config.MaxRetries ?? DEFAULT_MAX_RETRIES;
555
- const timeoutMs = config.RequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
556
- const minInterval = config.MinRequestIntervalMs ?? DEFAULT_MIN_REQUEST_INTERVAL_MS;
557
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
558
- await this.Throttle(minInterval);
559
- const outcome = await this.ExecuteSoapAttempt(endpoint, envelope, soapAction, timeoutMs);
560
- if (outcome.retryable && attempt < maxRetries) {
561
- const delay = Math.min(Math.pow(2, attempt) * 1000, 15000);
562
- await this.Sleep(delay);
563
- continue;
564
- }
565
- if (outcome.error)
566
- throw outcome.error;
567
- return outcome.xml;
568
- }
569
- throw new Error(`MagnetMailConnector: exhausted ${maxRetries + 1} attempts for SOAP action "${action}"`);
570
- }
571
- async ExecuteSoapAttempt(endpoint, envelope, soapAction, timeoutMs) {
572
- const controller = new AbortController();
573
- const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
574
- try {
575
- const response = await fetch(endpoint, {
576
- method: 'POST',
577
- headers: {
578
- 'Content-Type': 'text/xml; charset=utf-8',
579
- 'SOAPAction': `"${soapAction}"`,
580
- 'Accept': 'text/xml',
581
- },
582
- body: envelope,
583
- signal: controller.signal,
584
- });
585
- this.lastRequestTime = Date.now();
586
- const xml = await response.text();
587
- if (response.status === 429 || response.status >= 500) {
588
- return { error: new Error(`MagnetMail HTTP ${response.status}: ${xml.slice(0, 500)}`), retryable: true };
589
- }
590
- if (!response.ok) {
591
- return { error: new Error(`MagnetMail HTTP ${response.status}: ${xml.slice(0, 500)}`), retryable: false };
592
- }
593
- this.CheckSoapFault(xml);
594
- return { xml, retryable: false };
595
- }
596
- catch (err) {
597
- const message = err instanceof Error ? err.message : String(err);
598
- const retryable = message.includes('abort') || message.includes('timeout') || message.includes('ECONNRESET');
599
- return { error: new Error(`MagnetMail SOAP request failed: ${message}`), retryable };
600
- }
601
- finally {
602
- clearTimeout(timeoutHandle);
603
- }
604
- }
605
- // ── XML parsing (regex-based, consistent with SageIntacct approach) ──
606
- /**
607
- * Throws a descriptive error if the SOAP response contains a fault element.
608
- */
609
- CheckSoapFault(xml) {
610
- const faultMatch = xml.match(/<soap:Fault>([\s\S]*?)<\/soap:Fault>/i);
611
- if (!faultMatch)
612
- return;
613
- const fault = faultMatch[1];
614
- const code = this.ExtractNodeText(fault, 'faultcode') ?? 'UNKNOWN';
615
- const reason = this.ExtractNodeText(fault, 'faultstring') ?? this.ExtractNodeText(fault, 'Reason') ?? 'unknown';
616
- throw new Error(`MagnetMail SOAP Fault ${code}: ${reason}`);
617
- }
618
- /**
619
- * Extracts the text content of the first element with the given tag name.
620
- * Namespace-agnostic — matches by local name.
621
- */
622
- ExtractNodeText(xml, tag) {
623
- const regex = new RegExp(`<(?:\\w+:)?${this.EscapeRegex(tag)}[^>]*>([\\s\\S]*?)<\\/(?:\\w+:)?${this.EscapeRegex(tag)}>`, 'i');
624
- const match = xml.match(regex);
625
- return match ? this.DecodeXmlEntities(match[1].trim()) : null;
626
- }
627
- /**
628
- * Extracts a node nested inside another wrapper node (e.g., `<FooResult><id>42</id></FooResult>`).
629
- */
630
- ExtractWrappedNodeText(xml, wrapperTag, innerTag) {
631
- const wrapperRegex = new RegExp(`<(?:\\w+:)?${this.EscapeRegex(wrapperTag)}[^>]*>([\\s\\S]*?)<\\/(?:\\w+:)?${this.EscapeRegex(wrapperTag)}>`, 'i');
632
- const wrapperMatch = xml.match(wrapperRegex);
633
- if (!wrapperMatch)
634
- return null;
635
- return this.ExtractNodeText(wrapperMatch[1], innerTag);
636
- }
637
- /**
638
- * Parses an array of records inside a result wrapper. Returns one plain
639
- * object per record element. Works for both singular wrapper responses
640
- * (e.g., GetMessageDetails) and array-style (e.g., getMessagesUTC).
641
- */
642
- ParseRecordArray(xml, resultPath) {
643
- const containerXml = resultPath ? this.FindElementContent(xml, resultPath) : xml;
644
- if (!containerXml)
645
- return [];
646
- const childElements = this.ExtractDirectChildElements(containerXml);
647
- if (childElements.length === 0)
648
- return [];
649
- // Group consecutive same-tag elements as a record array
650
- const records = [];
651
- for (const child of childElements) {
652
- records.push(this.ParseElementFields(child.content));
653
- }
654
- return records;
655
- }
656
- FindElementContent(xml, tag) {
657
- const regex = new RegExp(`<(?:\\w+:)?${this.EscapeRegex(tag)}[^>]*>([\\s\\S]*?)<\\/(?:\\w+:)?${this.EscapeRegex(tag)}>`, 'i');
658
- const match = xml.match(regex);
659
- return match ? match[1] : null;
660
- }
661
- ExtractDirectChildElements(xml) {
662
- const results = [];
663
- const regex = /<(?:\w+:)?(\w+)(?:\s[^>]*)?>([\s\S]*?)<\/(?:\w+:)?\1>/g;
664
- let match;
665
- while ((match = regex.exec(xml)) !== null) {
666
- results.push({ tag: match[1], content: match[2] });
667
- }
668
- return results;
669
- }
670
- /**
671
- * Parses a record element's children into a flat key-value map. Nested
672
- * objects and arrays are returned as raw inner XML strings (caller can
673
- * re-parse if needed — simple enough for most MagnetMail fields).
674
- */
675
- ParseElementFields(xml) {
676
- const record = {};
677
- const children = this.ExtractDirectChildElements(xml);
678
- for (const child of children) {
679
- const value = this.CoerceFieldValue(child.content.trim());
680
- // Last-write-wins for repeated child elements; MagnetMail rarely emits them in these shapes
681
- record[child.tag] = value;
682
- }
683
- return record;
684
- }
685
- CoerceFieldValue(raw) {
686
- if (raw === '')
687
- return null;
688
- if (raw === 'true')
689
- return true;
690
- if (raw === 'false')
691
- return false;
692
- if (/^-?\d+$/.test(raw))
693
- return parseInt(raw, 10);
694
- if (/^-?\d+\.\d+$/.test(raw))
695
- return parseFloat(raw);
696
- return this.DecodeXmlEntities(raw);
697
- }
698
- // ── Metadata helpers ─────────────────────────────────────────────
699
- ResolveObject(integrationID, objectName) {
700
- const obj = IntegrationEngineBase.Instance.GetIntegrationObject(integrationID, objectName);
701
- if (!obj)
702
- throw new Error(`MagnetMailConnector: unknown IntegrationObject "${objectName}"`);
703
- return obj;
704
- }
705
- BuildObjectMeta(obj) {
706
- const listAction = obj.APIPath || '';
707
- if (!listAction)
708
- throw new Error(`MagnetMailConnector: IntegrationObject "${obj.Name}" is missing APIPath (SOAP action)`);
709
- const resultPath = obj.ResponseDataKey || `${listAction}Result`;
710
- const raw = this.ParseDefaultQueryParams(obj);
711
- const extraArgs = {};
712
- for (const [k, v] of Object.entries(raw)) {
713
- if (RESERVED_META_KEYS.has(k))
714
- continue;
715
- extraArgs[k] = String(v);
716
- }
717
- return {
718
- ListAction: listAction,
719
- ResultPath: resultPath,
720
- CreateAction: raw['create_action'],
721
- UpdateAction: raw['update_action'],
722
- DeleteAction: raw['delete_action'],
723
- DetailAction: raw['detail_action'],
724
- WatermarkField: raw['watermark_field'],
725
- WatermarkFromParam: raw['watermark_from_param'],
726
- WatermarkToParam: raw['watermark_to_param'],
727
- ExtraArgs: extraArgs,
728
- };
729
- }
730
- ParseDefaultQueryParams(obj) {
731
- if (!obj.DefaultQueryParams)
732
- return {};
733
- try {
734
- return JSON.parse(obj.DefaultQueryParams);
735
- }
736
- catch {
737
- console.warn(`[MagnetMailConnector] Invalid DefaultQueryParams JSON for "${obj.Name}"`);
738
- return {};
739
- }
740
- }
741
- GetPrimaryKeyField(objectID) {
742
- const fields = IntegrationEngineBase.Instance.GetIntegrationObjectFields(objectID);
743
- const pk = fields.find(f => f.IsPrimaryKey);
744
- return pk ? pk.Name : 'id';
745
- }
746
- BuildFetchArgs(meta, ctx, offset, pageSize) {
747
- const args = { ...meta.ExtraArgs };
748
- // Vendor WSDL: pagination uses 1-based pageNumber + pageCount on the
749
- // operations that accept pagination. Some operations (searchForRecipients,
750
- // getMessagesUTC) accept NO pagination — meta.SupportsPagination guards.
751
- if (meta.SupportsPagination !== false) {
752
- const pageNumber = Math.floor(offset / Math.max(1, pageSize)) + 1;
753
- args['pageNumber'] = String(pageNumber);
754
- args['pageCount'] = String(pageSize);
755
- }
756
- if (ctx.WatermarkValue && meta.WatermarkFromParam) {
757
- args[meta.WatermarkFromParam] = ctx.WatermarkValue;
758
- }
759
- return args;
760
- }
761
- RawToExternalRecord(raw, objectType, pkField) {
762
- const id = raw[pkField];
763
- return {
764
- ExternalID: id != null ? String(id) : '',
765
- ObjectType: objectType,
766
- Fields: raw,
767
- };
768
- }
769
- ComputeWatermark(records, field) {
770
- let latest;
771
- for (const record of records) {
772
- const value = record[field];
773
- if (value && typeof value === 'string' && (!latest || value > latest)) {
774
- latest = value;
775
- }
776
- }
777
- return latest;
778
- }
779
- // ── Utility helpers ──────────────────────────────────────────────
780
- async Throttle(minIntervalMs) {
781
- const elapsed = Date.now() - this.lastRequestTime;
782
- if (elapsed < minIntervalMs) {
783
- await this.Sleep(minIntervalMs - elapsed);
784
- }
785
- }
786
- Sleep(ms) {
787
- return new Promise(resolve => setTimeout(resolve, ms));
788
- }
789
- EscapeXmlValue(value) {
790
- return value
791
- .replace(/&/g, '&amp;')
792
- .replace(/</g, '&lt;')
793
- .replace(/>/g, '&gt;')
794
- .replace(/"/g, '&quot;')
795
- .replace(/'/g, '&apos;');
796
- }
797
- EscapeRegex(value) {
798
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
799
- }
800
- DecodeXmlEntities(value) {
801
- return value
802
- .replace(/&lt;/g, '<')
803
- .replace(/&gt;/g, '>')
804
- .replace(/&quot;/g, '"')
805
- .replace(/&apos;/g, "'")
806
- .replace(/&amp;/g, '&');
807
- }
808
- BuildCRUDError(err, operation, objectName) {
809
- const message = err instanceof Error ? err.message : String(err);
810
- return {
811
- Success: false,
812
- ErrorMessage: `${operation} failed for ${objectName}: ${message}`,
813
- StatusCode: 500,
814
- };
815
- }
816
- };
817
- MagnetMailConnector = __decorate([
818
- RegisterClass(BaseIntegrationConnector, 'MagnetMailConnector')
819
- ], MagnetMailConnector);
820
- export { MagnetMailConnector };
821
- /** Tree-shaking prevention function — import and call from module entry point. */
822
- export function LoadMagnetMailConnector() { }
823
- //# sourceMappingURL=MagnetMailConnector.js.map