@memberjunction/connector-netforum-enterprise 1.0.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/dist/NetForumConnector.d.ts +169 -0
- package/dist/NetForumConnector.js +778 -0
- package/dist/NetForumConnector.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { type UserInfo } from '@memberjunction/core';
|
|
2
|
+
import type { MJCompanyIntegrationEntity } from '@memberjunction/core-entities';
|
|
3
|
+
import { BaseRESTIntegrationConnector, type RESTAuthContext, type RESTResponse, type PaginationState, type PaginationType, type ConnectionTestResult, type FetchContext, type FetchBatchResult, type CreateRecordContext, type UpdateRecordContext, type DeleteRecordContext, type CRUDResult, type ExternalObjectSchema, type ExternalFieldSchema } from '@memberjunction/integration-engine';
|
|
4
|
+
export interface NetForumConnectionConfig {
|
|
5
|
+
BaseURL: string;
|
|
6
|
+
Username: string;
|
|
7
|
+
Password: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class NetForumConnector extends BaseRESTIntegrationConnector {
|
|
10
|
+
private tokenCache;
|
|
11
|
+
private lastRequestTime;
|
|
12
|
+
get IntegrationName(): string;
|
|
13
|
+
get SupportsCreate(): boolean;
|
|
14
|
+
get SupportsUpdate(): boolean;
|
|
15
|
+
get SupportsDelete(): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* §7 — discovery is NOT an authoritative full-gamut enumeration. `DiscoverObjects` returns the
|
|
18
|
+
* Declared baseline (engine cache) and GetQueryDefinition only extends per-object fields; neither
|
|
19
|
+
* enumerates the complete set of objects the credentials expose (customer-installed queries vary).
|
|
20
|
+
* So absence in a refresh proves nothing → the comprehensive-refresh deactivation path must stay
|
|
21
|
+
* off. Returning false keeps the Declared metadata safe from wrongful deactivation.
|
|
22
|
+
*/
|
|
23
|
+
get DiscoveryIsAuthoritative(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Step 1 of the two-step auth: POST a SOAP `Authenticate(userName, password)` envelope (which
|
|
26
|
+
* carries NO AuthorizationToken header — it is the bootstrap) and read the token string from
|
|
27
|
+
* `AuthenticateResult`. The token is cached and re-used until its TTL elapses or a 401 forces
|
|
28
|
+
* re-auth. Credentials go in the SOAP body elements — there is no HTTP Basic / Bearer here.
|
|
29
|
+
*/
|
|
30
|
+
protected Authenticate(ci: MJCompanyIntegrationEntity, cu: UserInfo): Promise<RESTAuthContext>;
|
|
31
|
+
private ParseConfig;
|
|
32
|
+
private ParseConfigFromCredential;
|
|
33
|
+
private ExtractConfig;
|
|
34
|
+
private StripTrailingSlash;
|
|
35
|
+
TestConnection(ci: MJCompanyIntegrationEntity, cu: UserInfo): Promise<ConnectionTestResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Standard objects = the Declared metadata (engine cache of persisted IntegrationObject rows).
|
|
38
|
+
* NO hardcoded catalog. The base implementation reads exactly the credential-free Declared
|
|
39
|
+
* universe; a live credential only ADDS customer-installed query objects (the Discovered
|
|
40
|
+
* extension), it never supplies the baseline — so this re-yields the standard universe
|
|
41
|
+
* credential-free and the runtime structure self-check passes without a token.
|
|
42
|
+
*/
|
|
43
|
+
DiscoverObjects(ci: MJCompanyIntegrationEntity, cu: UserInfo): Promise<ExternalObjectSchema[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Two-stage field discovery. Stage 1 returns the Declared field set (engine cache). Stage 2, when
|
|
46
|
+
* a credential is available, calls the GetQueryDefinition MECHANISM and parses the returned SQL
|
|
47
|
+
* column definition (Object → ListTable → ListFromTables → Column[]) into ExternalFieldSchema —
|
|
48
|
+
* this surfaces customer-added columns the static metadata never saw. With no credential, or on
|
|
49
|
+
* any error, it degrades to the Declared fields (never throws, never bakes a catalog).
|
|
50
|
+
*/
|
|
51
|
+
DiscoverFields(ci: MJCompanyIntegrationEntity, objectName: string, cu: UserInfo): Promise<ExternalFieldSchema[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Parses a GetQueryDefinition response (the SQL column definition) into ExternalFieldSchema[].
|
|
54
|
+
* Each `<Column>` carries mdc_name / mdc_description / mdc_data_type / mdc_nullable /
|
|
55
|
+
* mdc_width_max. Declared metadata wins for PK identity (a column definition does not mark a PK);
|
|
56
|
+
* discovery contributes the full column corpus + provable nullability/length.
|
|
57
|
+
*/
|
|
58
|
+
private ParseQueryDefinition;
|
|
59
|
+
private MapSoapType;
|
|
60
|
+
/**
|
|
61
|
+
* Fetches a batch via the GetQuery door. Walks the IO's access path from Configuration: the door
|
|
62
|
+
* (GetQuery), the query object name, and any top modifier (@TOP -1 to bypass the DataGrid row
|
|
63
|
+
* cap). Incremental sync uses the IO's per-facade `IncrementalWatermarkField` in the
|
|
64
|
+
* szWhereClause — there is NO canonical LastModifiedDate; the field is metadata-driven.
|
|
65
|
+
*
|
|
66
|
+
* Full-record pass-through: every parsed row becomes ExternalRecord.Fields verbatim — never a
|
|
67
|
+
* filtered/narrow literal — so the framework's custom-column capture sees every column the query
|
|
68
|
+
* returned, including customer-added ones.
|
|
69
|
+
*/
|
|
70
|
+
FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
|
|
71
|
+
private EscapeSqlLiteral;
|
|
72
|
+
/**
|
|
73
|
+
* Create via the SOAP operation named in the IO's CreateBodyKey / Configuration.writeOps.createOp
|
|
74
|
+
* (e.g. WEBIndividualInsert, InsertFacadeObject). The attributes are wrapped in a SOAP envelope
|
|
75
|
+
* for that operation; the new key is read from the response and routed through BuildCreatedResult,
|
|
76
|
+
* so a 2xx that carries no record key FAILS loudly (the silent-create-loss invariant).
|
|
77
|
+
*
|
|
78
|
+
* Overridden (not the generic per-operation REST path) because the body must be a SOAP envelope,
|
|
79
|
+
* not a JSON body — the generic CreateRecord builds a JSON body. We still honor the metadata
|
|
80
|
+
* columns (operation name) and still call BuildCreatedResult.
|
|
81
|
+
*/
|
|
82
|
+
CreateRecord(ctx: CreateRecordContext): Promise<CRUDResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Update via the SOAP operation named in UpdateBodyKey / Configuration.writeOps.updateOp.
|
|
85
|
+
* netFORUM exposes no ETag / If-Match — updates are last-write-wins.
|
|
86
|
+
*/
|
|
87
|
+
UpdateRecord(ctx: UpdateRecordContext): Promise<CRUDResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Delete is not supported. DeleteFacadeObject is not confirmed in any reachable vendor doc and
|
|
90
|
+
* netFORUM prefers soft-delete via status flags. SupportsDelete is false, so this returns a clean
|
|
91
|
+
* error rather than silently claiming success.
|
|
92
|
+
*/
|
|
93
|
+
DeleteRecord(ctx: DeleteRecordContext): Promise<CRUDResult>;
|
|
94
|
+
private ExtractKeyFromResponse;
|
|
95
|
+
/**
|
|
96
|
+
* Returns the IO's stable, monotonic ordering column (the PK / Configuration.stableOrderingKey)
|
|
97
|
+
* so the engine can keyset-resume objects that have no incremental watermark. Null when the IO is
|
|
98
|
+
* unknown or has no stable key — keyset resume is simply unavailable then, not an error.
|
|
99
|
+
*/
|
|
100
|
+
StableOrderingKey(objectName: string): string | null;
|
|
101
|
+
/** Required SOAP 1.1 HTTP headers for a given operation. */
|
|
102
|
+
private SoapHeaders;
|
|
103
|
+
/**
|
|
104
|
+
* Builds a SOAP 1.1 envelope for `operation` with the given body args. When a token is provided
|
|
105
|
+
* (every call except Authenticate), the AuthorizationToken header element carries it — this is
|
|
106
|
+
* the documented SOAP token carrier, NOT an HTTP Authorization header. Values are XML-escaped.
|
|
107
|
+
*/
|
|
108
|
+
private BuildSoapEnvelope;
|
|
109
|
+
/** Serializes one SOAP arg. Objects become nested elements; scalars become escaped text. */
|
|
110
|
+
private ArgElement;
|
|
111
|
+
private EscapeXml;
|
|
112
|
+
private UnescapeXml;
|
|
113
|
+
/**
|
|
114
|
+
* Extracts the text content of the FIRST occurrence of a scalar element by local name, ignoring
|
|
115
|
+
* namespace prefixes. Returns undefined when absent. Used for AuthenticateResult, mdc_* columns,
|
|
116
|
+
* and create-result keys.
|
|
117
|
+
*/
|
|
118
|
+
private ParseSoapScalar;
|
|
119
|
+
/** Returns the inner XML of every occurrence of an element by local name (namespace-agnostic). */
|
|
120
|
+
private ExtractElements;
|
|
121
|
+
private EscapeRegExp;
|
|
122
|
+
/**
|
|
123
|
+
* Strips the SOAP envelope and the GetQueryResult wrapper, returning each flattened row as a
|
|
124
|
+
* plain object of column→value. netFORUM GetQuery returns a DataSet of repeated row elements
|
|
125
|
+
* (the row element name is the query's "Result"/"<Object>" element). We locate the GetQueryResult
|
|
126
|
+
* payload, then treat every direct child element that itself contains child elements as a row.
|
|
127
|
+
*/
|
|
128
|
+
protected NormalizeResponse(rawBody: unknown, _key: string | null): Record<string, unknown>[];
|
|
129
|
+
/**
|
|
130
|
+
* Parses a flattened row set. The DataSet shape is `<Results><Result>...cols...</Result>...` (the
|
|
131
|
+
* outer/inner names vary by query, e.g. IndividualObjects/IndividualObject). We find the most
|
|
132
|
+
* common repeated leaf-bearing element name and treat each as a row of column elements.
|
|
133
|
+
*/
|
|
134
|
+
private ParseRowSet;
|
|
135
|
+
/** Parses one row's column elements into a flat object (full-record — every column kept). */
|
|
136
|
+
private ParseRowColumns;
|
|
137
|
+
/**
|
|
138
|
+
* Returns the local names of the direct (top-level) child elements of an XML fragment, by a
|
|
139
|
+
* depth scan over open/close/self-close tags (so a tag opened at depth 0 is a direct child).
|
|
140
|
+
*/
|
|
141
|
+
private TopLevelTagNames;
|
|
142
|
+
protected ExtractPaginationInfo(_rb: unknown, _pt: PaginationType, _cp: number, _co: number, _ps: number): PaginationState;
|
|
143
|
+
protected GetBaseURL(_ci: MJCompanyIntegrationEntity, auth: RESTAuthContext): string;
|
|
144
|
+
/** The base-class abstract BuildHeaders — defers to SoapHeaders for the active operation. */
|
|
145
|
+
protected BuildHeaders(_auth: RESTAuthContext): Record<string, string>;
|
|
146
|
+
/** The base-class abstract MakeHTTPRequest — routes through the raw transport. */
|
|
147
|
+
protected MakeHTTPRequest(_auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, body?: unknown): Promise<RESTResponse>;
|
|
148
|
+
/**
|
|
149
|
+
* Raw SOAP transport: POSTs the XML envelope, retries on 401 (re-auth)/429/5xx, and returns the
|
|
150
|
+
* response body as text (SOAP is text/xml). Never logs credentials, the token, or PII.
|
|
151
|
+
*
|
|
152
|
+
* `protected` so a unit-test subclass can override this single transport seam to feed canned
|
|
153
|
+
* SOAP responses (no live network, no credentials) — the only seam tests need to mock.
|
|
154
|
+
*/
|
|
155
|
+
protected MakeRawHTTPRequest(url: string, method: string, headers: Record<string, string>, body?: string): Promise<RESTResponse>;
|
|
156
|
+
private RetryAfterMs;
|
|
157
|
+
ExtractRetryAfterMs(error: unknown): number | undefined;
|
|
158
|
+
private Throttle;
|
|
159
|
+
private Sleep;
|
|
160
|
+
/** Remembered integration ID from the last fetch, so StableOrderingKey can read the cache. */
|
|
161
|
+
private LastIntegrationID;
|
|
162
|
+
private ParseObjectConfig;
|
|
163
|
+
private SoapEndpoint;
|
|
164
|
+
private PrimaryKeyFieldName;
|
|
165
|
+
/** Lowercased names of the IO's declared primary-key fields, read from the cached IOF entities. */
|
|
166
|
+
private DeclaredPrimaryKeyNames;
|
|
167
|
+
private AsText;
|
|
168
|
+
}
|
|
169
|
+
export declare function LoadNetForumConnector(): void;
|
|
@@ -0,0 +1,778 @@
|
|
|
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
|
+
* NetForumConnector — netFORUM Enterprise xWeb SOAP/XML integration connector.
|
|
9
|
+
*
|
|
10
|
+
* Protocol: SOAP 1.1 over HTTPS. There is NO `BaseSOAPIntegrationConnector` in the
|
|
11
|
+
* engine — the only protocol bases are `BaseIntegrationConnector` (grandparent) and
|
|
12
|
+
* `BaseRESTIntegrationConnector`. This connector rides `BaseRESTIntegrationConnector`
|
|
13
|
+
* and implements SOAP over its HTTP seam: it POSTs SOAP 1.1 envelopes to
|
|
14
|
+
* `/xweb/secure/netForumXML.asmx` via `MakeHTTPRequest` and parses the XML in
|
|
15
|
+
* `NormalizeResponse`. The transport is text/xml, not JSON.
|
|
16
|
+
*
|
|
17
|
+
* ── Wire truth (from the public WSDL: sources/netForumXML_asm.wsdl, 5.9 MB, 277 ops) ──
|
|
18
|
+
*
|
|
19
|
+
* Endpoint : POST <tenantHost>/xweb/secure/netForumXML.asmx (Content-Type: text/xml; charset=utf-8)
|
|
20
|
+
* Namespace : http://www.avectra.com/2005/ (Avectra-era, retained by Community Brands)
|
|
21
|
+
* SOAPAction : http://www.avectra.com/2005/<MethodName>
|
|
22
|
+
*
|
|
23
|
+
* Auth (TWO-STEP, SOAP — NOT HTTP Basic / WWW-Authenticate / Bearer):
|
|
24
|
+
* 1. Authenticate(userName, password) → AuthenticateResult (the token string).
|
|
25
|
+
* The Authenticate envelope carries NO AuthorizationToken header — it is the bootstrap.
|
|
26
|
+
* 2. Every subsequent data/CRUD call carries the token in the SOAP HEADER element:
|
|
27
|
+
* <AuthorizationToken xmlns="http://www.avectra.com/2005/"><Token>{token}</Token></AuthorizationToken>
|
|
28
|
+
* (NOT an HTTP Authorization header.) Source: WSDL `AuthorizationToken` complexType +
|
|
29
|
+
* `<soap:header part="AuthorizationToken">` on every WEB-prefixed / GetQuery operation binding.
|
|
30
|
+
*
|
|
31
|
+
* Read (door = GetQuery):
|
|
32
|
+
* GetQuery(szObjectName, szColumnList, szWhereClause?, szOrderBy?) → GetQueryResult
|
|
33
|
+
* - GetQueryResult is a `<s:any>` wildcard DataSet — flattened rows, one element per row.
|
|
34
|
+
* - Incremental: szWhereClause = "<watermarkField> >= '<wm>'" where the watermark column is
|
|
35
|
+
* the IO's per-facade `IncrementalWatermarkField` (e.g. ind_change_date, evt_*_change_date).
|
|
36
|
+
* There is NO canonical `LastModifiedDate` column — the field is per-facade and is read
|
|
37
|
+
* from metadata, never hardcoded.
|
|
38
|
+
* - Row limit: szObjectName supports an `@TOP -1` / `@TOP N` modifier to bypass / bound the
|
|
39
|
+
* server-side DataGrid row cap. The connector requests the modifier from the IO's accessPath.
|
|
40
|
+
*
|
|
41
|
+
* Discovery (mechanism, NOT a baked catalog):
|
|
42
|
+
* - Standard objects come from the Declared metadata (the persisted IntegrationObject rows in
|
|
43
|
+
* the engine cache — credential-free docs → static metadata, case 1). `DiscoverObjects`
|
|
44
|
+
* returns the cache; it does NOT enumerate a hardcoded `NF_OBJECTS` array (that was the bug
|
|
45
|
+
* this IMPROVE round removes).
|
|
46
|
+
* - Per-object field definitions come from `GetQueryDefinition(szObjectName)` at runtime
|
|
47
|
+
* (credential-gated, case 2): the response carries the SQL column definition
|
|
48
|
+
* (Object → ListTable → ListFromTables → ListFromTable[] → Columns → Column[]). The connector
|
|
49
|
+
* parses that into ExternalFieldSchema. With no credential it falls back to the Declared
|
|
50
|
+
* fields. The customer's own installed query objects (variable per installation) are reached
|
|
51
|
+
* the same way — GetQueryDefinition is the discovery MECHANISM, never an answer baked in code.
|
|
52
|
+
* - `DiscoveryIsAuthoritative` is FALSE: discovery extends the Declared baseline, it does not
|
|
53
|
+
* enumerate the full credential gamut, so absence in a refresh must NEVER deactivate.
|
|
54
|
+
*
|
|
55
|
+
* Write (facade CRUD — only where metadata declares the capability + per-operation columns):
|
|
56
|
+
* - Create/Update route through the IO's per-operation columns (CreateAPIPath/CreateMethod/
|
|
57
|
+
* CreateBodyShape/CreateBodyKey/CreateIDLocation, Update*) which name the SOAP operation
|
|
58
|
+
* (e.g. WEBIndividualInsert, InsertFacadeObject). The connector wraps the attributes in a
|
|
59
|
+
* SOAP envelope for that operation and routes the result through BuildCreatedResult so a 2xx
|
|
60
|
+
* with no record key fails loudly (no silent record loss / duplicate-create next sync).
|
|
61
|
+
* - SupportsDelete = false. `DeleteFacadeObject` is not confirmed in any reachable vendor doc;
|
|
62
|
+
* netFORUM prefers soft-delete via status flags. Held false until verified.
|
|
63
|
+
*
|
|
64
|
+
* Vendor doc URLs:
|
|
65
|
+
* - WSDL (primary) : https://myasm.asm.org/xweb/secure/netforumxml.asmx?WSDL
|
|
66
|
+
* - xWeb Overview : https://documentation.abila.com/netforum-enterprise/2017.1/Content/xWeb/XWeb_Overview.htm
|
|
67
|
+
* - GetQuery : https://documentation.abila.com/netforum-enterprise/2017.1/Content/xWeb/Methods/GetQuery.htm
|
|
68
|
+
*/
|
|
69
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
70
|
+
import { Metadata } from '@memberjunction/core';
|
|
71
|
+
import { BaseIntegrationConnector, BaseRESTIntegrationConnector, } from '@memberjunction/integration-engine';
|
|
72
|
+
// ─── Constants ───────────────────────────────────────────────────────
|
|
73
|
+
/** Avectra-era SOAP namespace, retained by Community Brands. All ops + types use it. */
|
|
74
|
+
const SOAP_NS = 'http://www.avectra.com/2005/';
|
|
75
|
+
/** Canonical xWeb SOAP endpoint path (per-tenant host; path is constant). */
|
|
76
|
+
const DEFAULT_SOAP_PATH = '/xweb/secure/netForumXML.asmx';
|
|
77
|
+
const MAX_RETRIES = 3;
|
|
78
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
79
|
+
const MIN_REQUEST_INTERVAL_MS = 200;
|
|
80
|
+
/** Session-token TTL. netFORUM does not publish a token lifetime; re-auth on 401 covers expiry. */
|
|
81
|
+
const TOKEN_TTL_MS = 3_600_000;
|
|
82
|
+
let NetForumConnector = class NetForumConnector extends BaseRESTIntegrationConnector {
|
|
83
|
+
constructor() {
|
|
84
|
+
super(...arguments);
|
|
85
|
+
this.tokenCache = null;
|
|
86
|
+
this.lastRequestTime = 0;
|
|
87
|
+
// ─── Per-object config helpers ───────────────────────────────────
|
|
88
|
+
/** Remembered integration ID from the last fetch, so StableOrderingKey can read the cache. */
|
|
89
|
+
this.LastIntegrationID = null;
|
|
90
|
+
}
|
|
91
|
+
get IntegrationName() { return 'NetForum Enterprise'; }
|
|
92
|
+
// Capability flags — kept in lockstep with the metadata's per-operation columns.
|
|
93
|
+
// Create/Update are declared on a subset of IOs (those with WEB*Insert/Update or a facade op);
|
|
94
|
+
// the generic CRUD path throws for an IO whose columns are null, so a flag here only asserts
|
|
95
|
+
// "at least one IO supports it". Delete stays false (DeleteFacadeObject unconfirmed in docs).
|
|
96
|
+
get SupportsCreate() { return true; }
|
|
97
|
+
get SupportsUpdate() { return true; }
|
|
98
|
+
get SupportsDelete() { return false; }
|
|
99
|
+
/**
|
|
100
|
+
* §7 — discovery is NOT an authoritative full-gamut enumeration. `DiscoverObjects` returns the
|
|
101
|
+
* Declared baseline (engine cache) and GetQueryDefinition only extends per-object fields; neither
|
|
102
|
+
* enumerates the complete set of objects the credentials expose (customer-installed queries vary).
|
|
103
|
+
* So absence in a refresh proves nothing → the comprehensive-refresh deactivation path must stay
|
|
104
|
+
* off. Returning false keeps the Declared metadata safe from wrongful deactivation.
|
|
105
|
+
*/
|
|
106
|
+
get DiscoveryIsAuthoritative() { return false; }
|
|
107
|
+
// ─── Auth — two-step SOAP token ──────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Step 1 of the two-step auth: POST a SOAP `Authenticate(userName, password)` envelope (which
|
|
110
|
+
* carries NO AuthorizationToken header — it is the bootstrap) and read the token string from
|
|
111
|
+
* `AuthenticateResult`. The token is cached and re-used until its TTL elapses or a 401 forces
|
|
112
|
+
* re-auth. Credentials go in the SOAP body elements — there is no HTTP Basic / Bearer here.
|
|
113
|
+
*/
|
|
114
|
+
async Authenticate(ci, cu) {
|
|
115
|
+
const config = await this.ParseConfig(ci, cu);
|
|
116
|
+
if (this.tokenCache && this.tokenCache.ExpiresAt > Date.now() + 60_000) {
|
|
117
|
+
return { Token: this.tokenCache.Token, Config: config };
|
|
118
|
+
}
|
|
119
|
+
const body = this.BuildSoapEnvelope('Authenticate', {
|
|
120
|
+
userName: config.Username,
|
|
121
|
+
password: config.Password,
|
|
122
|
+
});
|
|
123
|
+
const url = `${config.BaseURL}${DEFAULT_SOAP_PATH}`;
|
|
124
|
+
const headers = this.SoapHeaders('Authenticate');
|
|
125
|
+
const response = await this.MakeRawHTTPRequest(url, 'POST', headers, body);
|
|
126
|
+
if (response.Status < 200 || response.Status >= 300) {
|
|
127
|
+
throw new Error(`NetForum Authenticate failed: HTTP ${response.Status}`);
|
|
128
|
+
}
|
|
129
|
+
const token = this.ParseSoapScalar(this.AsText(response.Body), 'AuthenticateResult');
|
|
130
|
+
if (!token) {
|
|
131
|
+
throw new Error('NetForum Authenticate response contained no token (AuthenticateResult element)');
|
|
132
|
+
}
|
|
133
|
+
this.tokenCache = { Token: token, ExpiresAt: Date.now() + TOKEN_TTL_MS };
|
|
134
|
+
return { Token: token, Config: config };
|
|
135
|
+
}
|
|
136
|
+
async ParseConfig(ci, cu, provider) {
|
|
137
|
+
if (ci.CredentialID) {
|
|
138
|
+
const fromCred = await this.ParseConfigFromCredential(ci.CredentialID, cu, provider);
|
|
139
|
+
if (fromCred)
|
|
140
|
+
return fromCred;
|
|
141
|
+
}
|
|
142
|
+
if (ci.Configuration) {
|
|
143
|
+
const parsed = JSON.parse(ci.Configuration);
|
|
144
|
+
return this.ExtractConfig(parsed);
|
|
145
|
+
}
|
|
146
|
+
throw new Error('NetForum connector requires either CredentialID or Configuration JSON');
|
|
147
|
+
}
|
|
148
|
+
async ParseConfigFromCredential(credentialID, cu, provider) {
|
|
149
|
+
const md = provider ?? new Metadata();
|
|
150
|
+
const cred = await md.GetEntityObject('MJ: Credentials', cu);
|
|
151
|
+
const loaded = await cred.Load(credentialID);
|
|
152
|
+
if (!loaded || !cred.Values)
|
|
153
|
+
return null;
|
|
154
|
+
const values = JSON.parse(cred.Values);
|
|
155
|
+
return this.ExtractConfig(values);
|
|
156
|
+
}
|
|
157
|
+
ExtractConfig(values) {
|
|
158
|
+
const get = (...keys) => {
|
|
159
|
+
for (const key of keys) {
|
|
160
|
+
const hit = Object.entries(values).find(([k]) => k.toLowerCase() === key.toLowerCase());
|
|
161
|
+
if (hit)
|
|
162
|
+
return String(hit[1] ?? '');
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
};
|
|
166
|
+
const baseURL = get('BaseURL', 'BaseUrl', 'base_url');
|
|
167
|
+
const username = get('Username', 'username', 'user', 'userName');
|
|
168
|
+
const password = get('Password', 'password', 'pass');
|
|
169
|
+
if (!baseURL)
|
|
170
|
+
throw new Error('NetForum configuration missing required field: BaseURL');
|
|
171
|
+
if (!username)
|
|
172
|
+
throw new Error('NetForum configuration missing required field: Username');
|
|
173
|
+
if (!password)
|
|
174
|
+
throw new Error('NetForum configuration missing required field: Password');
|
|
175
|
+
return { BaseURL: this.StripTrailingSlash(baseURL), Username: username, Password: password };
|
|
176
|
+
}
|
|
177
|
+
StripTrailingSlash(s) { return s.replace(/\/+$/, ''); }
|
|
178
|
+
async TestConnection(ci, cu) {
|
|
179
|
+
try {
|
|
180
|
+
const auth = await this.Authenticate(ci, cu);
|
|
181
|
+
// Read-only smoke: GetVersion is a harmless, token-gated SystemInfo op.
|
|
182
|
+
const url = `${auth.Config.BaseURL}${DEFAULT_SOAP_PATH}`;
|
|
183
|
+
const body = this.BuildSoapEnvelope('GetVersion', {}, auth.Token);
|
|
184
|
+
const r = await this.MakeRawHTTPRequest(url, 'POST', this.SoapHeaders('GetVersion'), body);
|
|
185
|
+
return r.Status >= 200 && r.Status < 300
|
|
186
|
+
? { Success: true, Message: 'Connected to netFORUM Enterprise xWeb (SOAP)' }
|
|
187
|
+
: { Success: false, Message: `xWeb returned HTTP ${r.Status}` };
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
return { Success: false, Message: err instanceof Error ? err.message : String(err) };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ─── Discovery — Declared cache + runtime GetQueryDefinition ──────
|
|
194
|
+
/**
|
|
195
|
+
* Standard objects = the Declared metadata (engine cache of persisted IntegrationObject rows).
|
|
196
|
+
* NO hardcoded catalog. The base implementation reads exactly the credential-free Declared
|
|
197
|
+
* universe; a live credential only ADDS customer-installed query objects (the Discovered
|
|
198
|
+
* extension), it never supplies the baseline — so this re-yields the standard universe
|
|
199
|
+
* credential-free and the runtime structure self-check passes without a token.
|
|
200
|
+
*/
|
|
201
|
+
async DiscoverObjects(ci, cu) {
|
|
202
|
+
return super.DiscoverObjects(ci, cu);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Two-stage field discovery. Stage 1 returns the Declared field set (engine cache). Stage 2, when
|
|
206
|
+
* a credential is available, calls the GetQueryDefinition MECHANISM and parses the returned SQL
|
|
207
|
+
* column definition (Object → ListTable → ListFromTables → Column[]) into ExternalFieldSchema —
|
|
208
|
+
* this surfaces customer-added columns the static metadata never saw. With no credential, or on
|
|
209
|
+
* any error, it degrades to the Declared fields (never throws, never bakes a catalog).
|
|
210
|
+
*/
|
|
211
|
+
async DiscoverFields(ci, objectName, cu) {
|
|
212
|
+
const declared = await super.DiscoverFields(ci, objectName, cu);
|
|
213
|
+
// The base FieldEntityToSchema folds PK into IsUniqueKey and never sets IsPrimaryKey, so
|
|
214
|
+
// re-derive the honest IsPrimaryKey from the cached IOF entities (which carry it explicitly).
|
|
215
|
+
const pkNames = this.DeclaredPrimaryKeyNames(ci, objectName);
|
|
216
|
+
const declaredByName = new Map(declared.map(f => [f.Name.toLowerCase(), { ...f, IsPrimaryKey: pkNames.has(f.Name.toLowerCase()) }]));
|
|
217
|
+
try {
|
|
218
|
+
const auth = await this.Authenticate(ci, cu);
|
|
219
|
+
const url = `${auth.Config.BaseURL}${DEFAULT_SOAP_PATH}`;
|
|
220
|
+
const body = this.BuildSoapEnvelope('GetQueryDefinition', { szObjectName: objectName }, auth.Token);
|
|
221
|
+
const r = await this.MakeRawHTTPRequest(url, 'POST', this.SoapHeaders('GetQueryDefinition'), body);
|
|
222
|
+
if (r.Status >= 200 && r.Status < 300) {
|
|
223
|
+
const discovered = this.ParseQueryDefinition(this.AsText(r.Body), declaredByName);
|
|
224
|
+
if (discovered.length > 0)
|
|
225
|
+
return discovered;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// credential-free / network / parse failure → fall through to Declared
|
|
230
|
+
}
|
|
231
|
+
return declared;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Parses a GetQueryDefinition response (the SQL column definition) into ExternalFieldSchema[].
|
|
235
|
+
* Each `<Column>` carries mdc_name / mdc_description / mdc_data_type / mdc_nullable /
|
|
236
|
+
* mdc_width_max. Declared metadata wins for PK identity (a column definition does not mark a PK);
|
|
237
|
+
* discovery contributes the full column corpus + provable nullability/length.
|
|
238
|
+
*/
|
|
239
|
+
ParseQueryDefinition(xml, declaredByName) {
|
|
240
|
+
const out = [];
|
|
241
|
+
const seen = new Set();
|
|
242
|
+
for (const colXml of this.ExtractElements(xml, 'Column')) {
|
|
243
|
+
const name = this.ParseSoapScalar(colXml, 'mdc_name');
|
|
244
|
+
if (!name || seen.has(name.toLowerCase()))
|
|
245
|
+
continue;
|
|
246
|
+
seen.add(name.toLowerCase());
|
|
247
|
+
const declared = declaredByName.get(name.toLowerCase());
|
|
248
|
+
const dataType = this.ParseSoapScalar(colXml, 'mdc_data_type');
|
|
249
|
+
const description = this.ParseSoapScalar(colXml, 'mdc_description');
|
|
250
|
+
const nullable = this.ParseSoapScalar(colXml, 'mdc_nullable');
|
|
251
|
+
const widthRaw = this.ParseSoapScalar(colXml, 'mdc_width_max');
|
|
252
|
+
const maxLength = widthRaw && /^\d+$/.test(widthRaw) ? Number(widthRaw) : null;
|
|
253
|
+
// AllowsNull provable-only: 'mdc_nullable' is an explicit source flag. "0"/"false"/"no" ⇒ NOT NULL.
|
|
254
|
+
const allowsNull = nullable == null
|
|
255
|
+
? undefined
|
|
256
|
+
: !/^(0|false|no|n)$/i.test(nullable.trim());
|
|
257
|
+
out.push({
|
|
258
|
+
Name: name,
|
|
259
|
+
Label: declared?.Label ?? name,
|
|
260
|
+
Description: declared?.Description ?? (description || undefined),
|
|
261
|
+
DataType: declared?.DataType ?? this.MapSoapType(dataType ?? null),
|
|
262
|
+
IsRequired: declared?.IsRequired ?? false,
|
|
263
|
+
AllowsNull: allowsNull,
|
|
264
|
+
IsPrimaryKey: declared?.IsPrimaryKey ?? false,
|
|
265
|
+
IsUniqueKey: declared?.IsUniqueKey ?? false,
|
|
266
|
+
IsReadOnly: declared?.IsReadOnly ?? false,
|
|
267
|
+
IsForeignKey: declared?.IsForeignKey ?? false,
|
|
268
|
+
ForeignKeyTarget: declared?.ForeignKeyTarget ?? null,
|
|
269
|
+
MaxLength: maxLength,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// Re-add any Declared field GetQueryDefinition did not surface (provable-only baseline never lost).
|
|
273
|
+
for (const [key, f] of declaredByName) {
|
|
274
|
+
if (!seen.has(key))
|
|
275
|
+
out.push(f);
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
MapSoapType(sqlType) {
|
|
280
|
+
if (!sqlType)
|
|
281
|
+
return 'string';
|
|
282
|
+
const t = sqlType.toLowerCase();
|
|
283
|
+
if (/(date|time)/.test(t))
|
|
284
|
+
return 'datetime';
|
|
285
|
+
if (/(int|bigint|smallint|tinyint)/.test(t))
|
|
286
|
+
return 'number';
|
|
287
|
+
if (/(decimal|numeric|money|float|real)/.test(t))
|
|
288
|
+
return 'decimal';
|
|
289
|
+
if (/bit/.test(t))
|
|
290
|
+
return 'boolean';
|
|
291
|
+
if (/uniqueidentifier/.test(t))
|
|
292
|
+
return 'string';
|
|
293
|
+
return 'string';
|
|
294
|
+
}
|
|
295
|
+
// ─── FetchChanges — GetQuery door walk + per-facade watermark ─────
|
|
296
|
+
/**
|
|
297
|
+
* Fetches a batch via the GetQuery door. Walks the IO's access path from Configuration: the door
|
|
298
|
+
* (GetQuery), the query object name, and any top modifier (@TOP -1 to bypass the DataGrid row
|
|
299
|
+
* cap). Incremental sync uses the IO's per-facade `IncrementalWatermarkField` in the
|
|
300
|
+
* szWhereClause — there is NO canonical LastModifiedDate; the field is metadata-driven.
|
|
301
|
+
*
|
|
302
|
+
* Full-record pass-through: every parsed row becomes ExternalRecord.Fields verbatim — never a
|
|
303
|
+
* filtered/narrow literal — so the framework's custom-column capture sees every column the query
|
|
304
|
+
* returned, including customer-added ones.
|
|
305
|
+
*/
|
|
306
|
+
async FetchChanges(ctx) {
|
|
307
|
+
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
308
|
+
const obj = this.GetCachedObject(ctx.CompanyIntegration.IntegrationID, ctx.ObjectName);
|
|
309
|
+
const cfg = this.ParseObjectConfig(obj);
|
|
310
|
+
const accessPath = cfg.accessPath ?? {};
|
|
311
|
+
const queryObject = accessPath.queryObject ?? accessPath.doorArgs?.szObjectName ?? ctx.ObjectName;
|
|
312
|
+
const topModifier = accessPath.doorArgs?.topModifier ?? '@TOP -1';
|
|
313
|
+
const szObjectName = topModifier ? `${queryObject} ${topModifier}` : queryObject;
|
|
314
|
+
const args = { szObjectName, szColumnList: '*' };
|
|
315
|
+
const watermarkField = obj.IncrementalWatermarkField ?? undefined;
|
|
316
|
+
if (ctx.WatermarkValue && watermarkField) {
|
|
317
|
+
args.szWhereClause = `${watermarkField} >= '${this.EscapeSqlLiteral(ctx.WatermarkValue)}'`;
|
|
318
|
+
}
|
|
319
|
+
const orderingKey = cfg.stableOrderingKey ?? this.PrimaryKeyFieldName(obj);
|
|
320
|
+
if (orderingKey)
|
|
321
|
+
args.szOrderBy = orderingKey;
|
|
322
|
+
const url = `${auth.Config.BaseURL}${this.SoapEndpoint(cfg)}`;
|
|
323
|
+
const envelope = this.BuildSoapEnvelope('GetQuery', args, auth.Token);
|
|
324
|
+
const response = await this.MakeRawHTTPRequest(url, 'POST', this.SoapHeaders('GetQuery'), envelope);
|
|
325
|
+
if (response.Status < 200 || response.Status >= 300) {
|
|
326
|
+
throw new Error(`NetForum GetQuery(${ctx.ObjectName}) failed: HTTP ${response.Status}`);
|
|
327
|
+
}
|
|
328
|
+
const rows = this.NormalizeResponse(response.Body, null);
|
|
329
|
+
const pkField = this.PrimaryKeyFieldName(obj);
|
|
330
|
+
const warnings = rows.length === 0
|
|
331
|
+
? [{ Code: 'ZERO_ROWS', Message: `GetQuery(${szObjectName}) returned no rows.` }]
|
|
332
|
+
: undefined;
|
|
333
|
+
// Highest ordering-key value seen → keyset resume position when no watermark exists.
|
|
334
|
+
let maxKey;
|
|
335
|
+
const records = rows.map(row => {
|
|
336
|
+
const externalID = pkField ? String(row[pkField] ?? '') : '';
|
|
337
|
+
if (orderingKey) {
|
|
338
|
+
const v = row[orderingKey];
|
|
339
|
+
if (v != null) {
|
|
340
|
+
const s = String(v);
|
|
341
|
+
if (maxKey === undefined || s > maxKey)
|
|
342
|
+
maxKey = s;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { ExternalID: externalID, ObjectType: ctx.ObjectName, Fields: row };
|
|
346
|
+
});
|
|
347
|
+
// Watermark: when this IO has a watermark field, the engine narrows next sync from the max
|
|
348
|
+
// seen. GetQuery returns the full result set in one call → HasMore is always false.
|
|
349
|
+
let newWatermark;
|
|
350
|
+
if (watermarkField) {
|
|
351
|
+
for (const row of rows) {
|
|
352
|
+
const v = row[watermarkField];
|
|
353
|
+
if (v != null) {
|
|
354
|
+
const s = String(v);
|
|
355
|
+
if (newWatermark === undefined || s > newWatermark)
|
|
356
|
+
newWatermark = s;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
Records: records,
|
|
362
|
+
HasMore: false,
|
|
363
|
+
Warnings: warnings,
|
|
364
|
+
NewWatermarkValue: newWatermark,
|
|
365
|
+
NextAfterKeyValue: maxKey,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
EscapeSqlLiteral(v) { return v.replace(/'/g, "''"); }
|
|
369
|
+
// ─── CRUD — SOAP facade ops via per-operation metadata columns ────
|
|
370
|
+
/**
|
|
371
|
+
* Create via the SOAP operation named in the IO's CreateBodyKey / Configuration.writeOps.createOp
|
|
372
|
+
* (e.g. WEBIndividualInsert, InsertFacadeObject). The attributes are wrapped in a SOAP envelope
|
|
373
|
+
* for that operation; the new key is read from the response and routed through BuildCreatedResult,
|
|
374
|
+
* so a 2xx that carries no record key FAILS loudly (the silent-create-loss invariant).
|
|
375
|
+
*
|
|
376
|
+
* Overridden (not the generic per-operation REST path) because the body must be a SOAP envelope,
|
|
377
|
+
* not a JSON body — the generic CreateRecord builds a JSON body. We still honor the metadata
|
|
378
|
+
* columns (operation name) and still call BuildCreatedResult.
|
|
379
|
+
*/
|
|
380
|
+
async CreateRecord(ctx) {
|
|
381
|
+
const ci = ctx.CompanyIntegration;
|
|
382
|
+
const obj = this.GetCachedObject(ci.IntegrationID, ctx.ObjectName);
|
|
383
|
+
if (!obj.SupportsCreate) {
|
|
384
|
+
return { Success: false, StatusCode: 0, ErrorMessage: `CreateRecord not supported for "${ctx.ObjectName}".` };
|
|
385
|
+
}
|
|
386
|
+
const cfg = this.ParseObjectConfig(obj);
|
|
387
|
+
const operation = obj.CreateBodyKey ?? cfg.writeOps?.createOp;
|
|
388
|
+
if (!operation) {
|
|
389
|
+
return { Success: false, StatusCode: 0, ErrorMessage: `Create of "${ctx.ObjectName}" has no SOAP operation configured (CreateBodyKey).` };
|
|
390
|
+
}
|
|
391
|
+
const auth = await this.Authenticate(ci, ctx.ContextUser);
|
|
392
|
+
const url = `${auth.Config.BaseURL}${this.SoapEndpoint(cfg)}`;
|
|
393
|
+
const envelope = this.BuildSoapEnvelope(operation, ctx.Attributes, auth.Token);
|
|
394
|
+
const r = await this.MakeRawHTTPRequest(url, 'POST', this.SoapHeaders(operation), envelope);
|
|
395
|
+
if (r.Status < 200 || r.Status >= 300) {
|
|
396
|
+
return { Success: false, ExternalID: '', StatusCode: r.Status, ErrorMessage: `Create of "${ctx.ObjectName}" failed: HTTP ${r.Status}` };
|
|
397
|
+
}
|
|
398
|
+
const externalID = this.ExtractKeyFromResponse(r.Body);
|
|
399
|
+
return this.BuildCreatedResult(externalID, r.Status, ctx.ObjectName);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Update via the SOAP operation named in UpdateBodyKey / Configuration.writeOps.updateOp.
|
|
403
|
+
* netFORUM exposes no ETag / If-Match — updates are last-write-wins.
|
|
404
|
+
*/
|
|
405
|
+
async UpdateRecord(ctx) {
|
|
406
|
+
const ci = ctx.CompanyIntegration;
|
|
407
|
+
const obj = this.GetCachedObject(ci.IntegrationID, ctx.ObjectName);
|
|
408
|
+
if (!obj.SupportsUpdate) {
|
|
409
|
+
return { Success: false, ExternalID: ctx.ExternalID, StatusCode: 0, ErrorMessage: `UpdateRecord not supported for "${ctx.ObjectName}".` };
|
|
410
|
+
}
|
|
411
|
+
const cfg = this.ParseObjectConfig(obj);
|
|
412
|
+
const operation = obj.UpdateBodyKey ?? cfg.writeOps?.updateOp;
|
|
413
|
+
if (!operation) {
|
|
414
|
+
return { Success: false, ExternalID: ctx.ExternalID, StatusCode: 0, ErrorMessage: `Update of "${ctx.ObjectName}" has no SOAP operation configured (UpdateBodyKey).` };
|
|
415
|
+
}
|
|
416
|
+
const auth = await this.Authenticate(ci, ctx.ContextUser);
|
|
417
|
+
const url = `${auth.Config.BaseURL}${this.SoapEndpoint(cfg)}`;
|
|
418
|
+
const pkField = this.PrimaryKeyFieldName(obj);
|
|
419
|
+
const attributes = pkField
|
|
420
|
+
? { [pkField]: ctx.ExternalID, ...ctx.Attributes }
|
|
421
|
+
: { ...ctx.Attributes };
|
|
422
|
+
const envelope = this.BuildSoapEnvelope(operation, attributes, auth.Token);
|
|
423
|
+
const r = await this.MakeRawHTTPRequest(url, 'POST', this.SoapHeaders(operation), envelope);
|
|
424
|
+
if (r.Status >= 200 && r.Status < 300) {
|
|
425
|
+
return { Success: true, ExternalID: ctx.ExternalID, StatusCode: r.Status };
|
|
426
|
+
}
|
|
427
|
+
return { Success: false, ExternalID: ctx.ExternalID, StatusCode: r.Status, ErrorMessage: `Update of "${ctx.ObjectName}" failed: HTTP ${r.Status}` };
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Delete is not supported. DeleteFacadeObject is not confirmed in any reachable vendor doc and
|
|
431
|
+
* netFORUM prefers soft-delete via status flags. SupportsDelete is false, so this returns a clean
|
|
432
|
+
* error rather than silently claiming success.
|
|
433
|
+
*/
|
|
434
|
+
async DeleteRecord(ctx) {
|
|
435
|
+
return {
|
|
436
|
+
Success: false,
|
|
437
|
+
ExternalID: ctx.ExternalID,
|
|
438
|
+
StatusCode: 0,
|
|
439
|
+
ErrorMessage: 'NetForum connector does not support delete (DeleteFacadeObject unconfirmed; netFORUM uses soft-delete via status flags).',
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
ExtractKeyFromResponse(body) {
|
|
443
|
+
const xml = this.AsText(body);
|
|
444
|
+
// The Insert/Create ops return the new GUID — scan common result/key element names.
|
|
445
|
+
for (const tag of ['key', 'Key', 'AddResult', 'InsertResult', 'CreateResult', 'WEBIndividualInsertResult', 'cst_key']) {
|
|
446
|
+
const v = this.ParseSoapScalar(xml, tag);
|
|
447
|
+
if (v && v.trim().length > 0)
|
|
448
|
+
return v.trim();
|
|
449
|
+
}
|
|
450
|
+
// Fallback: first GUID-shaped token anywhere in the body.
|
|
451
|
+
const guid = xml.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/);
|
|
452
|
+
return guid ? guid[0] : undefined;
|
|
453
|
+
}
|
|
454
|
+
// ─── Stable ordering key (no-watermark resume) ───────────────────
|
|
455
|
+
/**
|
|
456
|
+
* Returns the IO's stable, monotonic ordering column (the PK / Configuration.stableOrderingKey)
|
|
457
|
+
* so the engine can keyset-resume objects that have no incremental watermark. Null when the IO is
|
|
458
|
+
* unknown or has no stable key — keyset resume is simply unavailable then, not an error.
|
|
459
|
+
*/
|
|
460
|
+
StableOrderingKey(objectName) {
|
|
461
|
+
const ci = this.LastIntegrationID;
|
|
462
|
+
if (!ci)
|
|
463
|
+
return null;
|
|
464
|
+
try {
|
|
465
|
+
const obj = this.GetCachedObject(ci, objectName);
|
|
466
|
+
const cfg = this.ParseObjectConfig(obj);
|
|
467
|
+
return cfg.stableOrderingKey ?? this.PrimaryKeyFieldName(obj) ?? null;
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// ─── SOAP envelope + XML helpers ─────────────────────────────────
|
|
474
|
+
/** Required SOAP 1.1 HTTP headers for a given operation. */
|
|
475
|
+
SoapHeaders(operation) {
|
|
476
|
+
return {
|
|
477
|
+
'Content-Type': 'text/xml; charset=utf-8',
|
|
478
|
+
'SOAPAction': `${SOAP_NS}${operation}`,
|
|
479
|
+
'User-Agent': 'MemberJunction-Integration/1.0',
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Builds a SOAP 1.1 envelope for `operation` with the given body args. When a token is provided
|
|
484
|
+
* (every call except Authenticate), the AuthorizationToken header element carries it — this is
|
|
485
|
+
* the documented SOAP token carrier, NOT an HTTP Authorization header. Values are XML-escaped.
|
|
486
|
+
*/
|
|
487
|
+
BuildSoapEnvelope(operation, args, token) {
|
|
488
|
+
const header = token
|
|
489
|
+
? `<soap:Header><AuthorizationToken xmlns="${SOAP_NS}"><Token>${this.EscapeXml(token)}</Token></AuthorizationToken></soap:Header>`
|
|
490
|
+
: '<soap:Header/>';
|
|
491
|
+
const argXml = Object.entries(args)
|
|
492
|
+
.map(([k, v]) => this.ArgElement(k, v))
|
|
493
|
+
.join('');
|
|
494
|
+
return `<?xml version="1.0" encoding="utf-8"?>` +
|
|
495
|
+
`<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">` +
|
|
496
|
+
header +
|
|
497
|
+
`<soap:Body><${operation} xmlns="${SOAP_NS}">${argXml}</${operation}></soap:Body>` +
|
|
498
|
+
`</soap:Envelope>`;
|
|
499
|
+
}
|
|
500
|
+
/** Serializes one SOAP arg. Objects become nested elements; scalars become escaped text. */
|
|
501
|
+
ArgElement(name, value) {
|
|
502
|
+
if (value === null || value === undefined)
|
|
503
|
+
return `<${name} />`;
|
|
504
|
+
if (typeof value === 'object') {
|
|
505
|
+
const inner = Object.entries(value)
|
|
506
|
+
.map(([k, v]) => this.ArgElement(k, v))
|
|
507
|
+
.join('');
|
|
508
|
+
return `<${name}>${inner}</${name}>`;
|
|
509
|
+
}
|
|
510
|
+
return `<${name}>${this.EscapeXml(String(value))}</${name}>`;
|
|
511
|
+
}
|
|
512
|
+
EscapeXml(s) {
|
|
513
|
+
return s
|
|
514
|
+
.replace(/&/g, '&')
|
|
515
|
+
.replace(/</g, '<')
|
|
516
|
+
.replace(/>/g, '>')
|
|
517
|
+
.replace(/"/g, '"')
|
|
518
|
+
.replace(/'/g, ''');
|
|
519
|
+
}
|
|
520
|
+
UnescapeXml(s) {
|
|
521
|
+
return s
|
|
522
|
+
.replace(/</g, '<')
|
|
523
|
+
.replace(/>/g, '>')
|
|
524
|
+
.replace(/"/g, '"')
|
|
525
|
+
.replace(/'/g, "'")
|
|
526
|
+
.replace(/&#(\d+);/g, (_m, d) => String.fromCharCode(Number(d)))
|
|
527
|
+
.replace(/&/g, '&');
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Extracts the text content of the FIRST occurrence of a scalar element by local name, ignoring
|
|
531
|
+
* namespace prefixes. Returns undefined when absent. Used for AuthenticateResult, mdc_* columns,
|
|
532
|
+
* and create-result keys.
|
|
533
|
+
*/
|
|
534
|
+
ParseSoapScalar(xml, localName) {
|
|
535
|
+
const re = new RegExp(`<(?:[\\w.-]+:)?${this.EscapeRegExp(localName)}\\b[^>]*>([\\s\\S]*?)</(?:[\\w.-]+:)?${this.EscapeRegExp(localName)}>`);
|
|
536
|
+
const m = re.exec(xml);
|
|
537
|
+
if (!m)
|
|
538
|
+
return undefined;
|
|
539
|
+
return this.UnescapeXml(m[1]).trim();
|
|
540
|
+
}
|
|
541
|
+
/** Returns the inner XML of every occurrence of an element by local name (namespace-agnostic). */
|
|
542
|
+
ExtractElements(xml, localName) {
|
|
543
|
+
const re = new RegExp(`<(?:[\\w.-]+:)?${this.EscapeRegExp(localName)}\\b[^>]*>([\\s\\S]*?)</(?:[\\w.-]+:)?${this.EscapeRegExp(localName)}>`, 'g');
|
|
544
|
+
const out = [];
|
|
545
|
+
let m;
|
|
546
|
+
while ((m = re.exec(xml)) !== null)
|
|
547
|
+
out.push(m[1]);
|
|
548
|
+
return out;
|
|
549
|
+
}
|
|
550
|
+
EscapeRegExp(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
551
|
+
/**
|
|
552
|
+
* Strips the SOAP envelope and the GetQueryResult wrapper, returning each flattened row as a
|
|
553
|
+
* plain object of column→value. netFORUM GetQuery returns a DataSet of repeated row elements
|
|
554
|
+
* (the row element name is the query's "Result"/"<Object>" element). We locate the GetQueryResult
|
|
555
|
+
* payload, then treat every direct child element that itself contains child elements as a row.
|
|
556
|
+
*/
|
|
557
|
+
NormalizeResponse(rawBody, _key) {
|
|
558
|
+
const xml = this.AsText(rawBody);
|
|
559
|
+
if (!xml)
|
|
560
|
+
return [];
|
|
561
|
+
// Unwrap the GetQueryResult body (the <s:any> DataSet). Fall back to the whole body.
|
|
562
|
+
const resultElems = this.ExtractElements(xml, 'GetQueryResult');
|
|
563
|
+
const payload = resultElems.length > 0 ? resultElems[0] : xml;
|
|
564
|
+
return this.ParseRowSet(payload);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Parses a flattened row set. The DataSet shape is `<Results><Result>...cols...</Result>...` (the
|
|
568
|
+
* outer/inner names vary by query, e.g. IndividualObjects/IndividualObject). We find the most
|
|
569
|
+
* common repeated leaf-bearing element name and treat each as a row of column elements.
|
|
570
|
+
*/
|
|
571
|
+
ParseRowSet(xml) {
|
|
572
|
+
// Identify candidate row element names: any tag that recurs AND contains nested elements.
|
|
573
|
+
const topTags = this.TopLevelTagNames(xml);
|
|
574
|
+
// The row wrapper is usually a single outer element (e.g. <Results>); descend one level if so.
|
|
575
|
+
let scope = xml;
|
|
576
|
+
if (topTags.length === 1) {
|
|
577
|
+
const inner = this.ExtractElements(xml, topTags[0]);
|
|
578
|
+
if (inner.length === 1 && /</.test(inner[0]))
|
|
579
|
+
scope = inner[0];
|
|
580
|
+
}
|
|
581
|
+
const rowTagCounts = new Map();
|
|
582
|
+
for (const tag of this.TopLevelTagNames(scope)) {
|
|
583
|
+
rowTagCounts.set(tag, (rowTagCounts.get(tag) ?? 0) + 1);
|
|
584
|
+
}
|
|
585
|
+
// Pick the most frequent row tag (ties → first). A single-row result still has count 1.
|
|
586
|
+
let rowTag;
|
|
587
|
+
let best = 0;
|
|
588
|
+
for (const [tag, count] of rowTagCounts) {
|
|
589
|
+
if (count > best) {
|
|
590
|
+
best = count;
|
|
591
|
+
rowTag = tag;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (!rowTag)
|
|
595
|
+
return [];
|
|
596
|
+
const rows = [];
|
|
597
|
+
for (const rowXml of this.ExtractElements(scope, rowTag)) {
|
|
598
|
+
const row = this.ParseRowColumns(rowXml);
|
|
599
|
+
if (Object.keys(row).length > 0)
|
|
600
|
+
rows.push(row);
|
|
601
|
+
}
|
|
602
|
+
return rows;
|
|
603
|
+
}
|
|
604
|
+
/** Parses one row's column elements into a flat object (full-record — every column kept). */
|
|
605
|
+
ParseRowColumns(rowXml) {
|
|
606
|
+
const row = {};
|
|
607
|
+
const re = /<([\w.-]+)\b[^>]*?(\/)?>(?:([\s\S]*?)<\/\1>)?/g;
|
|
608
|
+
let m;
|
|
609
|
+
while ((m = re.exec(rowXml)) !== null) {
|
|
610
|
+
const tag = m[1];
|
|
611
|
+
const selfClosing = m[2] === '/';
|
|
612
|
+
const content = m[3];
|
|
613
|
+
if (selfClosing || content === undefined) {
|
|
614
|
+
row[tag] = null;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
// Flattened GetQuery rows carry scalar columns; keep the unescaped text value.
|
|
618
|
+
row[tag] = this.UnescapeXml(content);
|
|
619
|
+
}
|
|
620
|
+
return row;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Returns the local names of the direct (top-level) child elements of an XML fragment, by a
|
|
624
|
+
* depth scan over open/close/self-close tags (so a tag opened at depth 0 is a direct child).
|
|
625
|
+
*/
|
|
626
|
+
TopLevelTagNames(xml) {
|
|
627
|
+
const names = [];
|
|
628
|
+
let depth = 0;
|
|
629
|
+
const tokenRe = /<\/?([\w.-]+)\b[^>]*?(\/)?>/g;
|
|
630
|
+
let t;
|
|
631
|
+
while ((t = tokenRe.exec(xml)) !== null) {
|
|
632
|
+
const full = t[0];
|
|
633
|
+
const name = t[1];
|
|
634
|
+
const selfClose = t[2] === '/';
|
|
635
|
+
const isClose = full.startsWith('</');
|
|
636
|
+
if (isClose) {
|
|
637
|
+
depth--;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (depth === 0)
|
|
641
|
+
names.push(name);
|
|
642
|
+
if (!selfClose)
|
|
643
|
+
depth++;
|
|
644
|
+
}
|
|
645
|
+
return names;
|
|
646
|
+
}
|
|
647
|
+
ExtractPaginationInfo(_rb, _pt, _cp, _co, _ps) {
|
|
648
|
+
// GetQuery returns the full result set in one call — no protocol-level pagination.
|
|
649
|
+
return { HasMore: false };
|
|
650
|
+
}
|
|
651
|
+
GetBaseURL(_ci, auth) {
|
|
652
|
+
return auth.Config.BaseURL;
|
|
653
|
+
}
|
|
654
|
+
/** The base-class abstract BuildHeaders — defers to SoapHeaders for the active operation. */
|
|
655
|
+
BuildHeaders(_auth) {
|
|
656
|
+
return { 'Content-Type': 'text/xml; charset=utf-8', 'User-Agent': 'MemberJunction-Integration/1.0' };
|
|
657
|
+
}
|
|
658
|
+
/** The base-class abstract MakeHTTPRequest — routes through the raw transport. */
|
|
659
|
+
async MakeHTTPRequest(_auth, url, method, headers, body) {
|
|
660
|
+
return this.MakeRawHTTPRequest(url, method, headers, typeof body === 'string' ? body : (body == null ? undefined : String(body)));
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Raw SOAP transport: POSTs the XML envelope, retries on 401 (re-auth)/429/5xx, and returns the
|
|
664
|
+
* response body as text (SOAP is text/xml). Never logs credentials, the token, or PII.
|
|
665
|
+
*
|
|
666
|
+
* `protected` so a unit-test subclass can override this single transport seam to feed canned
|
|
667
|
+
* SOAP responses (no live network, no credentials) — the only seam tests need to mock.
|
|
668
|
+
*/
|
|
669
|
+
async MakeRawHTTPRequest(url, method, headers, body) {
|
|
670
|
+
await this.Throttle();
|
|
671
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
672
|
+
const controller = new AbortController();
|
|
673
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
674
|
+
try {
|
|
675
|
+
const opts = { method, headers, signal: controller.signal };
|
|
676
|
+
if (body !== undefined && method !== 'GET')
|
|
677
|
+
opts.body = body;
|
|
678
|
+
const response = await fetch(url, opts);
|
|
679
|
+
clearTimeout(timer);
|
|
680
|
+
this.lastRequestTime = Date.now();
|
|
681
|
+
if (response.status === 401 && attempt === 0) {
|
|
682
|
+
this.tokenCache = null;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (response.status === 429 && attempt < MAX_RETRIES) {
|
|
686
|
+
await this.Sleep(this.RetryAfterMs(response, attempt));
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (response.status >= 500 && attempt < MAX_RETRIES) {
|
|
690
|
+
await this.Sleep(Math.min(1000 * Math.pow(2, attempt), 30_000));
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const text = await response.text();
|
|
694
|
+
const h = {};
|
|
695
|
+
response.headers.forEach((v, k) => { h[k.toLowerCase()] = v; });
|
|
696
|
+
return { Status: response.status, Body: text, Headers: h };
|
|
697
|
+
}
|
|
698
|
+
catch (e) {
|
|
699
|
+
clearTimeout(timer);
|
|
700
|
+
if (e instanceof Error && e.name === 'AbortError')
|
|
701
|
+
throw new Error(`NetForum request timed out: ${url}`);
|
|
702
|
+
if (attempt >= MAX_RETRIES)
|
|
703
|
+
throw e;
|
|
704
|
+
await this.Sleep(Math.min(1000 * Math.pow(2, attempt), 30_000));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
throw new Error(`NetForum request failed after ${MAX_RETRIES} retries: ${url}`);
|
|
708
|
+
}
|
|
709
|
+
RetryAfterMs(response, attempt) {
|
|
710
|
+
const ra = response.headers.get('retry-after');
|
|
711
|
+
if (ra) {
|
|
712
|
+
const secs = Number(ra);
|
|
713
|
+
if (!Number.isNaN(secs))
|
|
714
|
+
return Math.min(secs * 1000, 60_000);
|
|
715
|
+
}
|
|
716
|
+
return Math.min(1000 * Math.pow(2, attempt) + Math.random() * 500, 60_000);
|
|
717
|
+
}
|
|
718
|
+
ExtractRetryAfterMs(error) {
|
|
719
|
+
if (error && typeof error === 'object' && 'Headers' in error) {
|
|
720
|
+
const headers = error.Headers;
|
|
721
|
+
const ra = headers?.['retry-after'];
|
|
722
|
+
if (ra) {
|
|
723
|
+
const secs = Number(ra);
|
|
724
|
+
if (!Number.isNaN(secs))
|
|
725
|
+
return secs * 1000;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return undefined;
|
|
729
|
+
}
|
|
730
|
+
async Throttle() {
|
|
731
|
+
const elapsed = Date.now() - this.lastRequestTime;
|
|
732
|
+
if (elapsed < MIN_REQUEST_INTERVAL_MS)
|
|
733
|
+
await this.Sleep(MIN_REQUEST_INTERVAL_MS - elapsed);
|
|
734
|
+
}
|
|
735
|
+
Sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
736
|
+
ParseObjectConfig(obj) {
|
|
737
|
+
this.LastIntegrationID = obj.IntegrationID ?? this.LastIntegrationID;
|
|
738
|
+
if (!obj.Configuration)
|
|
739
|
+
return {};
|
|
740
|
+
try {
|
|
741
|
+
return JSON.parse(obj.Configuration);
|
|
742
|
+
}
|
|
743
|
+
catch {
|
|
744
|
+
return {};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
SoapEndpoint(cfg) {
|
|
748
|
+
return cfg.soapEndpoint ?? DEFAULT_SOAP_PATH;
|
|
749
|
+
}
|
|
750
|
+
PrimaryKeyFieldName(obj) {
|
|
751
|
+
const fields = this.GetCachedFields(obj.ID);
|
|
752
|
+
const pk = fields.find(f => f.IsPrimaryKey);
|
|
753
|
+
return pk?.Name;
|
|
754
|
+
}
|
|
755
|
+
/** Lowercased names of the IO's declared primary-key fields, read from the cached IOF entities. */
|
|
756
|
+
DeclaredPrimaryKeyNames(ci, objectName) {
|
|
757
|
+
try {
|
|
758
|
+
const obj = this.GetCachedObject(ci.IntegrationID, objectName);
|
|
759
|
+
return new Set(this.GetCachedFields(obj.ID).filter(f => f.IsPrimaryKey).map(f => f.Name.toLowerCase()));
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
return new Set();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
AsText(body) {
|
|
766
|
+
if (typeof body === 'string')
|
|
767
|
+
return body;
|
|
768
|
+
if (body == null)
|
|
769
|
+
return '';
|
|
770
|
+
return String(body);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
NetForumConnector = __decorate([
|
|
774
|
+
RegisterClass(BaseIntegrationConnector, '@memberjunction/connector-netforum-enterprise')
|
|
775
|
+
], NetForumConnector);
|
|
776
|
+
export { NetForumConnector };
|
|
777
|
+
export function LoadNetForumConnector() { }
|
|
778
|
+
//# sourceMappingURL=NetForumConnector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NetForumConnector.js","sourceRoot":"","sources":["../src/NetForumConnector.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from './NetForumConnector.js';
|
|
2
|
+
/** Open App bootstrap entry: importing this module ran the connector's @RegisterClass decorator;
|
|
3
|
+
* this no-op satisfies the loader's required startupExport and forces the import at MJAPI boot. */
|
|
4
|
+
export declare function registerConnector(): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from './NetForumConnector.js';
|
|
2
|
+
/** Open App bootstrap entry: importing this module ran the connector's @RegisterClass decorator;
|
|
3
|
+
* this no-op satisfies the loader's required startupExport and forces the import at MJAPI boot. */
|
|
4
|
+
export function registerConnector() { }
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AAEvC;oGACoG;AACpG,MAAM,UAAU,iBAAiB,KAAiD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memberjunction/connector-netforum-enterprise",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MemberJunction NetForum connector.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"/dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc && tsc-alias -f",
|
|
13
|
+
"test": "vitest run --passWithNoTests"
|
|
14
|
+
},
|
|
15
|
+
"author": "MemberJunction.com",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@memberjunction/core": ">=5.42.0 <6.0.0",
|
|
19
|
+
"@memberjunction/core-entities": ">=5.42.0 <6.0.0",
|
|
20
|
+
"@memberjunction/global": ">=5.42.0 <6.0.0",
|
|
21
|
+
"@memberjunction/integration-engine": ">=5.42.0 <6.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "24.10.11",
|
|
26
|
+
"tsc-alias": "^1.8.16",
|
|
27
|
+
"typescript": "^5.9.3",
|
|
28
|
+
"vitest": "^4.0.18",
|
|
29
|
+
"@memberjunction/core": "^5.42.0",
|
|
30
|
+
"@memberjunction/core-entities": "^5.42.0",
|
|
31
|
+
"@memberjunction/global": "^5.42.0",
|
|
32
|
+
"@memberjunction/integration-engine": "^5.42.0"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/MemberJunction/Integrations"
|
|
37
|
+
}
|
|
38
|
+
}
|