@memberjunction/connector-constant-contact 2.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.
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
2
|
+
import type { MJCompanyIntegrationEntity, MJIntegrationObjectEntity } from '@memberjunction/core-entities';
|
|
3
|
+
import { BaseRESTIntegrationConnector, type RESTAuthContext, type RESTResponse, type PaginationState, type PaginationType, type ConnectionTestResult, type FetchContext, type FetchBatchResult, type RateLimitPolicy, type SourceSchemaInfo, type CreateRecordContext, type CRUDResult } from '@memberjunction/integration-engine';
|
|
4
|
+
/**
|
|
5
|
+
* Constant Contact V3 connector — extends BaseRESTIntegrationConnector (REST/JSON over HTTP).
|
|
6
|
+
*
|
|
7
|
+
* Discovery, template-var read traversal (second-layer objects resolve their parent via
|
|
8
|
+
* Configuration.parentObjectName), and the paginated GET loop are inherited. This class supplies only the
|
|
9
|
+
* Constant Contact-specific protocol surface: OAuth2 auth with rotating-refresh persistence, cursor
|
|
10
|
+
* pagination, documented incremental filters, generic per-operation CRUD, and the §7/§10 sync-efficiency
|
|
11
|
+
* hooks the frozen contract evidences.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ConstantContactConnector extends BaseRESTIntegrationConnector {
|
|
14
|
+
/** One token manager per connector instance — caches the access token across a run + tracks rotation. */
|
|
15
|
+
private readonly tokenManager;
|
|
16
|
+
/** Cached auth context for the run (rebuilt when the token nears expiry). */
|
|
17
|
+
private cachedAuth;
|
|
18
|
+
/** The most recently seen/persisted rotating refresh token, so we only persist on an actual rotation. */
|
|
19
|
+
private currentRefreshToken;
|
|
20
|
+
/** In-flight watermark for the current FetchChanges — consumed by AppendDefaultQueryParams. */
|
|
21
|
+
private currentWatermark;
|
|
22
|
+
/** Verbatim `MJ: Integrations.Name`. Load-bearing: the T1 three-way name check compares this === metadata Name. */
|
|
23
|
+
get IntegrationName(): string;
|
|
24
|
+
get SupportsCreate(): boolean;
|
|
25
|
+
get SupportsUpdate(): boolean;
|
|
26
|
+
get SupportsDelete(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Discovery is NON-authoritative: DiscoverObjects / IntrospectSchema are cache-driven (they re-read
|
|
29
|
+
* persisted ACTIVE Declared metadata, NOT a live full-gamut enumeration). Constant Contact V3 exposes no
|
|
30
|
+
* complete describe endpoint — the OpenAPI spec is the only complete-schema source and is seeded at build
|
|
31
|
+
* as Declared metadata; per-tenant custom fields flow through runtime sampling + the framework's
|
|
32
|
+
* custom-column capture. Absence in a refresh proves nothing → never deactivate. Matches
|
|
33
|
+
* Configuration.DiscoveryIsAuthoritative=false in the frozen contract.
|
|
34
|
+
*/
|
|
35
|
+
get DiscoveryIsAuthoritative(): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* From Configuration.RateLimitPolicy: 4 requests/sec + 10,000/day per account. TokensPerSec=4 is the
|
|
38
|
+
* vendor-wide safe ceiling; the engine's AIMD bucket backs off on the documented 429 (`throttled` /
|
|
39
|
+
* `quota_exceeded`) bodies. No Retry-After header is documented (see ExtractRetryAfterMs left at default).
|
|
40
|
+
*/
|
|
41
|
+
get RateLimitPolicy(): RateLimitPolicy | null;
|
|
42
|
+
/** Conservative in-flight cap — 4 req/sec is the ceiling, so a small parallel fan-out is safe. */
|
|
43
|
+
get MaxConcurrencyHint(): number | null;
|
|
44
|
+
/**
|
|
45
|
+
* No-watermark objects resume by the object's StableOrderingKey — read from the IO metadata (the extractor
|
|
46
|
+
* emits `StableOrderingKey`; else the declared PK — Constant Contact's `<resource>_id`). Returns null when
|
|
47
|
+
* the object declares no stable key or the cache is unavailable (unit-test context).
|
|
48
|
+
*/
|
|
49
|
+
StableOrderingKey(objectName: string): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Sample-union enrichment (MJ connector standard): the Declared metadata is spec-derived and can miss a
|
|
52
|
+
* tenant's custom contact fields. After the base cache-driven introspection, sample each object's live read
|
|
53
|
+
* shape via `DiscoverFieldsViaFetch` and UNION it into the declared field set with
|
|
54
|
+
* `mergeDeclaredWithSampledFields` — never-shrink, declared-wins, capacities widened. Best-effort + parallel;
|
|
55
|
+
* a sample failure leaves the declared set untouched. NOTE: we override `IntrospectSchema` (NOT
|
|
56
|
+
* `DiscoverFields` — that would recurse into `DiscoverFieldsViaFetch`'s own fallback). Connector-agnostic.
|
|
57
|
+
*/
|
|
58
|
+
IntrospectSchema(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<SourceSchemaInfo>;
|
|
59
|
+
/**
|
|
60
|
+
* Mints/refreshes the OAuth2 access token via the shared OAuth2TokenManager (grant_type=refresh_token,
|
|
61
|
+
* HTTP-Basic client auth) — NO inline crypto. Constant Contact rotates the refresh token on every
|
|
62
|
+
* exchange; when the returned refresh token differs from the one we sent, we PERSIST the newest one back
|
|
63
|
+
* to the Credential record (a single-use token — the next refresh fails if we don't). Cached for the run.
|
|
64
|
+
*/
|
|
65
|
+
protected Authenticate(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<RESTAuthContext>;
|
|
66
|
+
/** Bearer auth header + JSON accept/content-type. */
|
|
67
|
+
protected BuildHeaders(auth: RESTAuthContext): Record<string, string>;
|
|
68
|
+
/** HTTP transport (fetch). Owns the wire boundary; test subclasses override this to capture requests. */
|
|
69
|
+
protected MakeHTTPRequest(_auth: RESTAuthContext, url: string, method: string, headers: Record<string, string>, body?: unknown): Promise<RESTResponse>;
|
|
70
|
+
/**
|
|
71
|
+
* Strips the Constant Contact list envelope to the per-object resource array (`{ contacts: [...] }` →
|
|
72
|
+
* ResponseDataKey='contacts'). A get-one / singleton response (`/contacts/{id}`, `/account/summary`) is a
|
|
73
|
+
* bare object with no resource key → returned as a single-element array. A bare array is returned as-is.
|
|
74
|
+
*/
|
|
75
|
+
protected NormalizeResponse(rawBody: unknown, responseDataKey: string | null): Record<string, unknown>[];
|
|
76
|
+
/**
|
|
77
|
+
* Constant Contact cursor pagination: the response carries `_links.next.href`
|
|
78
|
+
* (e.g. `/v3/contacts?cursor=<opaque>`). Extract the opaque `cursor` value and surface it as NextCursor;
|
|
79
|
+
* absence of `_links.next` (or PaginationType='None') ends the loop.
|
|
80
|
+
*/
|
|
81
|
+
protected ExtractPaginationInfo(rawBody: unknown, paginationType: PaginationType, _currentPage: number, _currentOffset: number, _pageSize: number): PaginationState;
|
|
82
|
+
/**
|
|
83
|
+
* Base API host. `/v3` version segment is embedded in CC_BASE_URL; each object's APIPath is appended.
|
|
84
|
+
* An explicit Configuration base-URL override (sandbox / test / mock) wins so the connector can be
|
|
85
|
+
* redirected by data alone — production sets no override.
|
|
86
|
+
*/
|
|
87
|
+
protected GetBaseURL(_companyIntegration: MJCompanyIntegrationEntity, auth: RESTAuthContext): string;
|
|
88
|
+
/**
|
|
89
|
+
* Constant Contact pagination params: first page `?limit=<n>`; subsequent pages `?cursor=<opaque>` (the
|
|
90
|
+
* opaque cursor already encodes the limit + any active incremental filter, so it is passed alone).
|
|
91
|
+
*/
|
|
92
|
+
protected BuildPaginatedURL(basePath: string, obj: MJIntegrationObjectEntity, _page: number, _offset: number, cursor?: string, effectivePageSize?: number): string;
|
|
93
|
+
/**
|
|
94
|
+
* Injects the documented incremental filter on the FIRST page of an incremental fetch. The filter param
|
|
95
|
+
* name varies per object (contacts → `updated_after`, emails → `after_date`) so it is read from the IO's
|
|
96
|
+
* Configuration.incrementalFilterFormat template (`<param>={value}`), never assumed. Skipped once a cursor
|
|
97
|
+
* is present — the opaque cursor already carries the filter forward, so re-appending would double it.
|
|
98
|
+
*/
|
|
99
|
+
protected AppendDefaultQueryParams(url: string, obj: MJIntegrationObjectEntity): string;
|
|
100
|
+
/**
|
|
101
|
+
* Wraps the inherited fetch to (1) expose the incremental watermark to AppendDefaultQueryParams, and
|
|
102
|
+
* (2) compute the new watermark from the max IncrementalWatermarkField value seen — advanced ONLY on a
|
|
103
|
+
* fully-drained batch (HasMore=false) so a mid-sync partial never persists a premature watermark. For
|
|
104
|
+
* non-incremental objects this is a transparent pass-through to the base fetch.
|
|
105
|
+
*/
|
|
106
|
+
FetchChanges(ctx: FetchContext): Promise<FetchBatchResult>;
|
|
107
|
+
/**
|
|
108
|
+
* OVERRIDDEN ONLY to branch Constant Contact's two idiosyncratic create shapes; the common case falls
|
|
109
|
+
* straight through to the GENERIC base `super.CreateRecord`:
|
|
110
|
+
* 1. Nested-resource create paths carry PARENT template vars (e.g.
|
|
111
|
+
* `/emails/activities/{campaign_activity_id}/abtest`, `/events/{event_id}/copy`) filled from the
|
|
112
|
+
* record's own Attributes — the base never templates create paths.
|
|
113
|
+
* 2. ASYNC BULK-ACTIVITY jobs (resourceTag "Bulk Activities"): a `POST /activities/*` returns a job
|
|
114
|
+
* handle whose completion is polled at `GET /activities/{activity_id}` until a terminal `state`.
|
|
115
|
+
* A plain create (no path vars, not a bulk job) delegates to the base so the generic per-operation path
|
|
116
|
+
* (+ the ExtractIDFromResponse named-PK seam + loud-on-empty-id BuildCreatedResult) runs unchanged.
|
|
117
|
+
*/
|
|
118
|
+
CreateRecord(ctx: CreateRecordContext): Promise<CRUDResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Narrow base seam (NOT a CreateRecord takeover): after the standard id/ID/Id scan, surface Constant
|
|
121
|
+
* Contact's NAMED primary key — the new resource is returned with a `<resource>_id` (`contact_id`,
|
|
122
|
+
* `list_id`, `segment_id`, `activity_id`, …). Reads the first top-level `*_id` field. This is what lets the
|
|
123
|
+
* GENERIC base `CreateRecord` handle every simple create with no override.
|
|
124
|
+
*/
|
|
125
|
+
protected ExtractIDFromResponse(response: RESTResponse, idLocation: string | null): string | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* IDIOSYNCRATIC — async bulk-activity completion poll. The `POST /activities/*` create returned a job
|
|
128
|
+
* handle (`activity_id`); poll `GET /activities/{activity_id}` until the `state` leaves `processing`.
|
|
129
|
+
* `completed` → success (ExternalID = activity_id, via the loud-on-empty BuildCreatedResult); any other
|
|
130
|
+
* terminal state (`cancelled`/`failed`/`time_out`/`unknown`) or an exhausted poll budget → a loud failure
|
|
131
|
+
* that still carries the activity_id so a re-sync is idempotent rather than duplicating the job.
|
|
132
|
+
*/
|
|
133
|
+
private finishAsyncBulkActivity;
|
|
134
|
+
/**
|
|
135
|
+
* OVERRIDDEN so the generic Update/Delete/Get path can template Constant Contact's NAMED single-record
|
|
136
|
+
* path vars (`{segment_id}`, `{event_id}`, `{campaign_activity_id}`, …) — the base only substitutes
|
|
137
|
+
* `{id}`/`{ExternalID}`. A single-var path takes the whole ExternalID; a multi-var (nested) path takes the
|
|
138
|
+
* composite `parent|child` ExternalID split in path order.
|
|
139
|
+
*/
|
|
140
|
+
protected SubstituteIDInPath(path: string, externalID: string, idLocation: string | null): string;
|
|
141
|
+
/**
|
|
142
|
+
* Tests the connection by hitting the account-summary endpoint. A 2xx confirms the OAuth token is valid;
|
|
143
|
+
* 401/403 → auth failure; anything else → error.
|
|
144
|
+
*/
|
|
145
|
+
TestConnection(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo): Promise<ConnectionTestResult>;
|
|
146
|
+
/** Reads the credential from the linked Credential entity (preferred) or the Configuration JSON fallback. */
|
|
147
|
+
private loadCredentials;
|
|
148
|
+
/** Loads a credential row and parses its Values JSON. */
|
|
149
|
+
private loadFromCredentialEntity;
|
|
150
|
+
/** Extracts Constant Contact OAuth2 credential fields from a credential/config JSON string. */
|
|
151
|
+
private parseCredentialJson;
|
|
152
|
+
/**
|
|
153
|
+
* Persists the newly-rotated refresh token (and current access token) back to the linked Credential
|
|
154
|
+
* record so the next cold-start refresh uses the correct single-use token. Best-effort: a persistence
|
|
155
|
+
* failure is logged, never thrown (the in-memory token still works for the current run).
|
|
156
|
+
*/
|
|
157
|
+
protected persistRotatedRefreshToken(companyIntegration: MJCompanyIntegrationEntity, contextUser: UserInfo, refreshToken: string, accessToken: string, provider?: IMetadataProvider): Promise<void>;
|
|
158
|
+
/** Reads the opaque `cursor` query-param value from a `_links.next.href` (relative or absolute). */
|
|
159
|
+
private extractCursorFromHref;
|
|
160
|
+
/** True when the object is an async Bulk-Activity job endpoint (poll-for-completion write shape). */
|
|
161
|
+
private isAsyncBulkActivity;
|
|
162
|
+
/** Reads the async-job `state` from a `GET /activities/{activity_id}` poll response (else 'unknown'). */
|
|
163
|
+
private readActivityState;
|
|
164
|
+
/** Poll delay — overridable in tests (a Mocked subclass returns immediately so the suite never blocks). */
|
|
165
|
+
protected sleep(ms: number): Promise<void>;
|
|
166
|
+
/** Substitutes CreateAPIPath parent template vars from the record's own attributes (nested creates). */
|
|
167
|
+
private substitutePathVarsFromAttributes;
|
|
168
|
+
/** Computes the new watermark = max IncrementalWatermarkField value seen (never below the current). */
|
|
169
|
+
private computeNewWatermark;
|
|
170
|
+
/** Reads an explicit full base-URL override from the connection Configuration (sandbox/test/mock only). */
|
|
171
|
+
private resolveBaseURLOverride;
|
|
172
|
+
/**
|
|
173
|
+
* Optional OAuth2 token-endpoint override from the connection Configuration. Constant Contact's
|
|
174
|
+
* default authz host (`CC_TOKEN_URL`) differs from the API host, so this is read independently of
|
|
175
|
+
* the base-URL override. Lets a deployment target a non-default authz host and makes the connector
|
|
176
|
+
* testable against a mock token endpoint. Falls back to `CC_TOKEN_URL` when unset.
|
|
177
|
+
*/
|
|
178
|
+
private resolveTokenURLOverride;
|
|
179
|
+
/** Joins a base URL and a path (mirrors the base's private BuildFullURL). */
|
|
180
|
+
private joinURL;
|
|
181
|
+
/** Gets an IO from the cache without throwing (used by StableOrderingKey, which may be called early). */
|
|
182
|
+
private tryGetCachedObject;
|
|
183
|
+
/** Reads a trimmed string value from an IntegrationObject's Configuration JSON (tolerant of absent/invalid). */
|
|
184
|
+
private readConfigString;
|
|
185
|
+
/** Reads a finite number value from an IntegrationObject's Configuration JSON (tolerant of absent/invalid). */
|
|
186
|
+
private readConfigNumber;
|
|
187
|
+
/** Returns the first present, non-empty string value among the given keys. */
|
|
188
|
+
private firstString;
|
|
189
|
+
}
|
|
190
|
+
/** Tree-shaking prevention — import and call from the module entry point. */
|
|
191
|
+
export declare function LoadConstantContactConnector(): void;
|
|
@@ -0,0 +1,686 @@
|
|
|
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
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
8
|
+
import { Metadata } from '@memberjunction/core';
|
|
9
|
+
import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
|
|
10
|
+
import { BaseIntegrationConnector, BaseRESTIntegrationConnector, OAuth2TokenManager, } from '@memberjunction/integration-engine';
|
|
11
|
+
import { mergeDeclaredWithSampledFields } from '@memberjunction/connector-schema-merge';
|
|
12
|
+
// ─── Constants ──────────────────────────────────────────────────────────
|
|
13
|
+
/** Constant Contact V3 API base URL (version embedded in the path segment). */
|
|
14
|
+
const CC_BASE_URL = 'https://api.cc.email/v3';
|
|
15
|
+
/** OAuth2 token endpoint (Authorization-Code flow). */
|
|
16
|
+
const CC_TOKEN_URL = 'https://authz.constantcontact.com/oauth2/default/v1/token';
|
|
17
|
+
/** Access-token lifetime fallback (seconds) when the token response omits expires_in — 24h per the vendor. */
|
|
18
|
+
const CC_ACCESS_TOKEN_LIFETIME_S = 86_400;
|
|
19
|
+
/** Re-mint when within this many ms of expiry. */
|
|
20
|
+
const CC_TOKEN_REFRESH_BUFFER_MS = 60_000;
|
|
21
|
+
/** Default page size when the IO metadata declares none (most Constant Contact collections default to 50). */
|
|
22
|
+
const CC_DEFAULT_PAGE_SIZE = 50;
|
|
23
|
+
// ─── Bulk-activity async-job state machine ────────────────────────────────
|
|
24
|
+
// Every `POST /activities/*` (resourceTag "Bulk Activities") returns an Activity JOB resource whose
|
|
25
|
+
// completion is polled via `GET /activities/{activity_id}`. The documented `state` values are
|
|
26
|
+
// processing | completed | cancelled | failed | time_out | unknown (from the V3 OpenAPI ActivityStatus).
|
|
27
|
+
/** The only non-terminal state — keep polling while the job reports this. */
|
|
28
|
+
const CC_BULK_STATE_PROCESSING = 'processing';
|
|
29
|
+
/** The success terminal state. */
|
|
30
|
+
const CC_BULK_STATE_COMPLETED = 'completed';
|
|
31
|
+
/** Fallback when the poll response carries no readable `state`. */
|
|
32
|
+
const CC_BULK_STATE_UNKNOWN = 'unknown';
|
|
33
|
+
/** Configuration.resourceTag that marks an object as an async Bulk-Activity job endpoint. */
|
|
34
|
+
const CC_BULK_ACTIVITY_TAG = 'Bulk Activities';
|
|
35
|
+
/** Default poll ceiling (attempts) + interval (ms) — ~2 min max; per-IO Configuration may override. */
|
|
36
|
+
const CC_BULK_POLL_MAX_ATTEMPTS = 60;
|
|
37
|
+
const CC_BULK_POLL_INTERVAL_MS = 2_000;
|
|
38
|
+
// ─── ConstantContactConnector ─────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Constant Contact V3 connector — extends BaseRESTIntegrationConnector (REST/JSON over HTTP).
|
|
41
|
+
*
|
|
42
|
+
* Discovery, template-var read traversal (second-layer objects resolve their parent via
|
|
43
|
+
* Configuration.parentObjectName), and the paginated GET loop are inherited. This class supplies only the
|
|
44
|
+
* Constant Contact-specific protocol surface: OAuth2 auth with rotating-refresh persistence, cursor
|
|
45
|
+
* pagination, documented incremental filters, generic per-operation CRUD, and the §7/§10 sync-efficiency
|
|
46
|
+
* hooks the frozen contract evidences.
|
|
47
|
+
*/
|
|
48
|
+
let ConstantContactConnector = class ConstantContactConnector extends BaseRESTIntegrationConnector {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(...arguments);
|
|
51
|
+
/** One token manager per connector instance — caches the access token across a run + tracks rotation. */
|
|
52
|
+
this.tokenManager = new OAuth2TokenManager();
|
|
53
|
+
/** Cached auth context for the run (rebuilt when the token nears expiry). */
|
|
54
|
+
this.cachedAuth = null;
|
|
55
|
+
/** In-flight watermark for the current FetchChanges — consumed by AppendDefaultQueryParams. */
|
|
56
|
+
this.currentWatermark = null;
|
|
57
|
+
}
|
|
58
|
+
// ── Identity (T1 three-way invariant) ────────────────────────────────
|
|
59
|
+
/** Verbatim `MJ: Integrations.Name`. Load-bearing: the T1 three-way name check compares this === metadata Name. */
|
|
60
|
+
get IntegrationName() {
|
|
61
|
+
return 'constant-contact';
|
|
62
|
+
}
|
|
63
|
+
// ── Capability getters (kept in lockstep with the per-op metadata columns) ──
|
|
64
|
+
get SupportsCreate() { return true; }
|
|
65
|
+
get SupportsUpdate() { return true; }
|
|
66
|
+
get SupportsDelete() { return true; }
|
|
67
|
+
/**
|
|
68
|
+
* Discovery is NON-authoritative: DiscoverObjects / IntrospectSchema are cache-driven (they re-read
|
|
69
|
+
* persisted ACTIVE Declared metadata, NOT a live full-gamut enumeration). Constant Contact V3 exposes no
|
|
70
|
+
* complete describe endpoint — the OpenAPI spec is the only complete-schema source and is seeded at build
|
|
71
|
+
* as Declared metadata; per-tenant custom fields flow through runtime sampling + the framework's
|
|
72
|
+
* custom-column capture. Absence in a refresh proves nothing → never deactivate. Matches
|
|
73
|
+
* Configuration.DiscoveryIsAuthoritative=false in the frozen contract.
|
|
74
|
+
*/
|
|
75
|
+
get DiscoveryIsAuthoritative() {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
// ── Sync-efficiency hooks (§7/§10 — populated from frozen-contract Configuration facts) ──
|
|
79
|
+
/**
|
|
80
|
+
* From Configuration.RateLimitPolicy: 4 requests/sec + 10,000/day per account. TokensPerSec=4 is the
|
|
81
|
+
* vendor-wide safe ceiling; the engine's AIMD bucket backs off on the documented 429 (`throttled` /
|
|
82
|
+
* `quota_exceeded`) bodies. No Retry-After header is documented (see ExtractRetryAfterMs left at default).
|
|
83
|
+
*/
|
|
84
|
+
get RateLimitPolicy() {
|
|
85
|
+
return { TokensPerSec: 4, Burst: 4 };
|
|
86
|
+
}
|
|
87
|
+
/** Conservative in-flight cap — 4 req/sec is the ceiling, so a small parallel fan-out is safe. */
|
|
88
|
+
get MaxConcurrencyHint() { return 2; }
|
|
89
|
+
/**
|
|
90
|
+
* No-watermark objects resume by the object's StableOrderingKey — read from the IO metadata (the extractor
|
|
91
|
+
* emits `StableOrderingKey`; else the declared PK — Constant Contact's `<resource>_id`). Returns null when
|
|
92
|
+
* the object declares no stable key or the cache is unavailable (unit-test context).
|
|
93
|
+
*/
|
|
94
|
+
StableOrderingKey(objectName) {
|
|
95
|
+
const obj = this.tryGetCachedObject(objectName);
|
|
96
|
+
if (!obj)
|
|
97
|
+
return null;
|
|
98
|
+
const declared = obj.StableOrderingKey;
|
|
99
|
+
if (declared && declared.trim().length > 0)
|
|
100
|
+
return declared.trim();
|
|
101
|
+
const pk = this.GetCachedFields(obj.ID).find(f => f.IsPrimaryKey);
|
|
102
|
+
return pk?.Name ?? null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Sample-union enrichment (MJ connector standard): the Declared metadata is spec-derived and can miss a
|
|
106
|
+
* tenant's custom contact fields. After the base cache-driven introspection, sample each object's live read
|
|
107
|
+
* shape via `DiscoverFieldsViaFetch` and UNION it into the declared field set with
|
|
108
|
+
* `mergeDeclaredWithSampledFields` — never-shrink, declared-wins, capacities widened. Best-effort + parallel;
|
|
109
|
+
* a sample failure leaves the declared set untouched. NOTE: we override `IntrospectSchema` (NOT
|
|
110
|
+
* `DiscoverFields` — that would recurse into `DiscoverFieldsViaFetch`'s own fallback). Connector-agnostic.
|
|
111
|
+
*/
|
|
112
|
+
async IntrospectSchema(companyIntegration, contextUser) {
|
|
113
|
+
const info = await super.IntrospectSchema(companyIntegration, contextUser);
|
|
114
|
+
await Promise.all(info.Objects.map(async (obj) => {
|
|
115
|
+
try {
|
|
116
|
+
const sampled = await this.DiscoverFieldsViaFetch(companyIntegration, obj.ExternalName, contextUser);
|
|
117
|
+
obj.Fields = mergeDeclaredWithSampledFields(obj.Fields, sampled);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* best-effort — a sample failure leaves the declared fields as-is */
|
|
121
|
+
}
|
|
122
|
+
}));
|
|
123
|
+
return info;
|
|
124
|
+
}
|
|
125
|
+
// ── Abstract REST hooks ──────────────────────────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* Mints/refreshes the OAuth2 access token via the shared OAuth2TokenManager (grant_type=refresh_token,
|
|
128
|
+
* HTTP-Basic client auth) — NO inline crypto. Constant Contact rotates the refresh token on every
|
|
129
|
+
* exchange; when the returned refresh token differs from the one we sent, we PERSIST the newest one back
|
|
130
|
+
* to the Credential record (a single-use token — the next refresh fails if we don't). Cached for the run.
|
|
131
|
+
*/
|
|
132
|
+
async Authenticate(companyIntegration, contextUser) {
|
|
133
|
+
if (this.cachedAuth && this.cachedAuth.ExpiresAt.getTime() > Date.now() + CC_TOKEN_REFRESH_BUFFER_MS) {
|
|
134
|
+
return this.cachedAuth;
|
|
135
|
+
}
|
|
136
|
+
const creds = await this.loadCredentials(companyIntegration, contextUser);
|
|
137
|
+
if (!creds.ClientId || !creds.ClientSecret) {
|
|
138
|
+
throw new Error('Constant Contact credential incomplete: ClientId and ClientSecret are required.');
|
|
139
|
+
}
|
|
140
|
+
if (!this.currentRefreshToken)
|
|
141
|
+
this.currentRefreshToken = creds.RefreshToken;
|
|
142
|
+
if (!this.currentRefreshToken) {
|
|
143
|
+
throw new Error('Constant Contact credential incomplete: a RefreshToken is required for the Authorization-Code flow.');
|
|
144
|
+
}
|
|
145
|
+
const token = await this.tokenManager.GetAccessToken({
|
|
146
|
+
TokenURL: this.resolveTokenURLOverride(companyIntegration) ?? CC_TOKEN_URL,
|
|
147
|
+
ClientId: creds.ClientId,
|
|
148
|
+
ClientSecret: creds.ClientSecret,
|
|
149
|
+
RefreshToken: this.currentRefreshToken,
|
|
150
|
+
UseBasicAuth: true,
|
|
151
|
+
}, 'refresh_token');
|
|
152
|
+
// Rotating refresh token: persist the newest value the moment it changes.
|
|
153
|
+
if (token.RefreshToken && token.RefreshToken !== this.currentRefreshToken) {
|
|
154
|
+
const rotated = token.RefreshToken;
|
|
155
|
+
this.currentRefreshToken = rotated;
|
|
156
|
+
await this.persistRotatedRefreshToken(companyIntegration, contextUser, rotated, token.AccessToken);
|
|
157
|
+
}
|
|
158
|
+
this.cachedAuth = {
|
|
159
|
+
Token: token.AccessToken,
|
|
160
|
+
ExpiresAt: new Date(token.ExpiresAt),
|
|
161
|
+
BaseURLOverride: this.resolveBaseURLOverride(companyIntegration) ?? undefined,
|
|
162
|
+
};
|
|
163
|
+
return this.cachedAuth;
|
|
164
|
+
}
|
|
165
|
+
/** Bearer auth header + JSON accept/content-type. */
|
|
166
|
+
BuildHeaders(auth) {
|
|
167
|
+
return {
|
|
168
|
+
'Authorization': `Bearer ${auth.Token}`,
|
|
169
|
+
'Accept': 'application/json',
|
|
170
|
+
'Content-Type': 'application/json',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** HTTP transport (fetch). Owns the wire boundary; test subclasses override this to capture requests. */
|
|
174
|
+
async MakeHTTPRequest(_auth, url, method, headers, body) {
|
|
175
|
+
const response = await fetch(url, {
|
|
176
|
+
method,
|
|
177
|
+
headers,
|
|
178
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
179
|
+
});
|
|
180
|
+
const respHeaders = {};
|
|
181
|
+
response.headers.forEach((v, k) => { respHeaders[k.toLowerCase()] = v; });
|
|
182
|
+
const text = await response.text();
|
|
183
|
+
let parsed = null;
|
|
184
|
+
if (text.length > 0) {
|
|
185
|
+
try {
|
|
186
|
+
parsed = JSON.parse(text);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
parsed = text;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { Status: response.status, Body: parsed, Headers: respHeaders };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Strips the Constant Contact list envelope to the per-object resource array (`{ contacts: [...] }` →
|
|
196
|
+
* ResponseDataKey='contacts'). A get-one / singleton response (`/contacts/{id}`, `/account/summary`) is a
|
|
197
|
+
* bare object with no resource key → returned as a single-element array. A bare array is returned as-is.
|
|
198
|
+
*/
|
|
199
|
+
NormalizeResponse(rawBody, responseDataKey) {
|
|
200
|
+
if (rawBody == null)
|
|
201
|
+
return [];
|
|
202
|
+
if (Array.isArray(rawBody))
|
|
203
|
+
return rawBody;
|
|
204
|
+
if (typeof rawBody !== 'object')
|
|
205
|
+
return [];
|
|
206
|
+
const body = rawBody;
|
|
207
|
+
if (responseDataKey && Array.isArray(body[responseDataKey])) {
|
|
208
|
+
return body[responseDataKey];
|
|
209
|
+
}
|
|
210
|
+
// Single-object / singleton response (get-one, account summary) — no list envelope present.
|
|
211
|
+
return [body];
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Constant Contact cursor pagination: the response carries `_links.next.href`
|
|
215
|
+
* (e.g. `/v3/contacts?cursor=<opaque>`). Extract the opaque `cursor` value and surface it as NextCursor;
|
|
216
|
+
* absence of `_links.next` (or PaginationType='None') ends the loop.
|
|
217
|
+
*/
|
|
218
|
+
ExtractPaginationInfo(rawBody, paginationType, _currentPage, _currentOffset, _pageSize) {
|
|
219
|
+
if (paginationType === 'None')
|
|
220
|
+
return { HasMore: false };
|
|
221
|
+
if (!rawBody || typeof rawBody !== 'object')
|
|
222
|
+
return { HasMore: false };
|
|
223
|
+
const href = rawBody._links?.next?.href;
|
|
224
|
+
if (typeof href !== 'string' || href.length === 0)
|
|
225
|
+
return { HasMore: false };
|
|
226
|
+
const cursor = this.extractCursorFromHref(href);
|
|
227
|
+
if (!cursor)
|
|
228
|
+
return { HasMore: false };
|
|
229
|
+
return { HasMore: true, NextCursor: cursor };
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Base API host. `/v3` version segment is embedded in CC_BASE_URL; each object's APIPath is appended.
|
|
233
|
+
* An explicit Configuration base-URL override (sandbox / test / mock) wins so the connector can be
|
|
234
|
+
* redirected by data alone — production sets no override.
|
|
235
|
+
*/
|
|
236
|
+
GetBaseURL(_companyIntegration, auth) {
|
|
237
|
+
const ctx = auth;
|
|
238
|
+
if (ctx.BaseURLOverride)
|
|
239
|
+
return ctx.BaseURLOverride.replace(/\/+$/, '');
|
|
240
|
+
return CC_BASE_URL;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Constant Contact pagination params: first page `?limit=<n>`; subsequent pages `?cursor=<opaque>` (the
|
|
244
|
+
* opaque cursor already encodes the limit + any active incremental filter, so it is passed alone).
|
|
245
|
+
*/
|
|
246
|
+
BuildPaginatedURL(basePath, obj, _page, _offset, cursor, effectivePageSize) {
|
|
247
|
+
const separator = basePath.includes('?') ? '&' : '?';
|
|
248
|
+
if (cursor)
|
|
249
|
+
return `${basePath}${separator}cursor=${encodeURIComponent(cursor)}`;
|
|
250
|
+
const pageSize = effectivePageSize ?? obj.DefaultPageSize ?? CC_DEFAULT_PAGE_SIZE;
|
|
251
|
+
return `${basePath}${separator}limit=${pageSize}`;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Injects the documented incremental filter on the FIRST page of an incremental fetch. The filter param
|
|
255
|
+
* name varies per object (contacts → `updated_after`, emails → `after_date`) so it is read from the IO's
|
|
256
|
+
* Configuration.incrementalFilterFormat template (`<param>={value}`), never assumed. Skipped once a cursor
|
|
257
|
+
* is present — the opaque cursor already carries the filter forward, so re-appending would double it.
|
|
258
|
+
*/
|
|
259
|
+
AppendDefaultQueryParams(url, obj) {
|
|
260
|
+
let result = super.AppendDefaultQueryParams(url, obj);
|
|
261
|
+
if (this.currentWatermark && obj.SupportsIncrementalSync && !/[?&]cursor=/.test(result)) {
|
|
262
|
+
const filterFormat = this.readConfigString(obj, 'incrementalFilterFormat');
|
|
263
|
+
if (filterFormat && filterFormat.includes('{value}')) {
|
|
264
|
+
const param = filterFormat.replace('{value}', encodeURIComponent(this.currentWatermark));
|
|
265
|
+
const separator = result.includes('?') ? '&' : '?';
|
|
266
|
+
result = `${result}${separator}${param}`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
// ── FetchChanges (incremental filter injection + watermark tracking) ──
|
|
272
|
+
/**
|
|
273
|
+
* Wraps the inherited fetch to (1) expose the incremental watermark to AppendDefaultQueryParams, and
|
|
274
|
+
* (2) compute the new watermark from the max IncrementalWatermarkField value seen — advanced ONLY on a
|
|
275
|
+
* fully-drained batch (HasMore=false) so a mid-sync partial never persists a premature watermark. For
|
|
276
|
+
* non-incremental objects this is a transparent pass-through to the base fetch.
|
|
277
|
+
*/
|
|
278
|
+
async FetchChanges(ctx) {
|
|
279
|
+
const obj = this.GetCachedObject(ctx.CompanyIntegration.IntegrationID, ctx.ObjectName);
|
|
280
|
+
this.currentWatermark = obj.SupportsIncrementalSync ? ctx.WatermarkValue : null;
|
|
281
|
+
try {
|
|
282
|
+
const result = await super.FetchChanges(ctx);
|
|
283
|
+
if (obj.SupportsIncrementalSync && !result.HasMore) {
|
|
284
|
+
const newWatermark = this.computeNewWatermark(obj, result.Records, ctx.WatermarkValue);
|
|
285
|
+
if (newWatermark)
|
|
286
|
+
result.NewWatermarkValue = newWatermark;
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
this.currentWatermark = null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ── CRUD ──────────────────────────────────────────────────────────────
|
|
295
|
+
//
|
|
296
|
+
// GENERIC per-operation dispatch is the default. Update / Delete / Get use the INHERITED base methods
|
|
297
|
+
// verbatim (they read the per-operation IO columns and route through the named-var SubstituteIDInPath
|
|
298
|
+
// override below). Simple synchronous creates ALSO use the inherited base — the only reason the base needs
|
|
299
|
+
// help is Constant Contact's named PK, which the narrow ExtractIDFromResponse override supplies. CreateRecord
|
|
300
|
+
// is overridden ONLY to branch the two idiosyncratic write shapes the generic path can't express.
|
|
301
|
+
/**
|
|
302
|
+
* OVERRIDDEN ONLY to branch Constant Contact's two idiosyncratic create shapes; the common case falls
|
|
303
|
+
* straight through to the GENERIC base `super.CreateRecord`:
|
|
304
|
+
* 1. Nested-resource create paths carry PARENT template vars (e.g.
|
|
305
|
+
* `/emails/activities/{campaign_activity_id}/abtest`, `/events/{event_id}/copy`) filled from the
|
|
306
|
+
* record's own Attributes — the base never templates create paths.
|
|
307
|
+
* 2. ASYNC BULK-ACTIVITY jobs (resourceTag "Bulk Activities"): a `POST /activities/*` returns a job
|
|
308
|
+
* handle whose completion is polled at `GET /activities/{activity_id}` until a terminal `state`.
|
|
309
|
+
* A plain create (no path vars, not a bulk job) delegates to the base so the generic per-operation path
|
|
310
|
+
* (+ the ExtractIDFromResponse named-PK seam + loud-on-empty-id BuildCreatedResult) runs unchanged.
|
|
311
|
+
*/
|
|
312
|
+
async CreateRecord(ctx) {
|
|
313
|
+
const ci = ctx.CompanyIntegration;
|
|
314
|
+
const contextUser = ctx.ContextUser;
|
|
315
|
+
const obj = this.GetCachedObject(ci.IntegrationID, ctx.ObjectName);
|
|
316
|
+
const needsPathVars = !!obj.CreateAPIPath && /\{\w+\}/.test(obj.CreateAPIPath);
|
|
317
|
+
const isAsyncBulk = this.isAsyncBulkActivity(obj);
|
|
318
|
+
// Common case → GENERIC base per-operation create (named PK surfaced by ExtractIDFromResponse).
|
|
319
|
+
if ((!needsPathVars && !isAsyncBulk) || !obj.CreateAPIPath || !obj.CreateMethod) {
|
|
320
|
+
return super.CreateRecord(ctx);
|
|
321
|
+
}
|
|
322
|
+
// Idiosyncratic path: nested create-path templating and/or async bulk-activity job.
|
|
323
|
+
const auth = await this.Authenticate(ci, contextUser);
|
|
324
|
+
const baseURL = this.GetBaseURL(ci, auth);
|
|
325
|
+
const headers = this.BuildHeaders(auth);
|
|
326
|
+
const path = this.substitutePathVarsFromAttributes(obj.CreateAPIPath, ctx.Attributes);
|
|
327
|
+
const url = this.joinURL(baseURL, path);
|
|
328
|
+
const body = this.BuildOperationBody(ctx.Attributes, obj.CreateBodyShape, obj.CreateBodyKey);
|
|
329
|
+
const response = await this.MakeHTTPRequest(auth, url, obj.CreateMethod, headers, body);
|
|
330
|
+
if (response.Status < 200 || response.Status >= 300) {
|
|
331
|
+
return {
|
|
332
|
+
Success: false,
|
|
333
|
+
StatusCode: response.Status,
|
|
334
|
+
ErrorMessage: this.ExtractErrorMessage(response) ?? `HTTP ${response.Status} on create`,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (isAsyncBulk) {
|
|
338
|
+
return this.finishAsyncBulkActivity(ctx.ObjectName, obj, auth, baseURL, headers, response);
|
|
339
|
+
}
|
|
340
|
+
const externalID = this.ExtractIDFromResponse(response, obj.CreateIDLocation);
|
|
341
|
+
return this.BuildCreatedResult(externalID, response.Status, ctx.ObjectName);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Narrow base seam (NOT a CreateRecord takeover): after the standard id/ID/Id scan, surface Constant
|
|
345
|
+
* Contact's NAMED primary key — the new resource is returned with a `<resource>_id` (`contact_id`,
|
|
346
|
+
* `list_id`, `segment_id`, `activity_id`, …). Reads the first top-level `*_id` field. This is what lets the
|
|
347
|
+
* GENERIC base `CreateRecord` handle every simple create with no override.
|
|
348
|
+
*/
|
|
349
|
+
ExtractIDFromResponse(response, idLocation) {
|
|
350
|
+
const generic = super.ExtractIDFromResponse(response, idLocation);
|
|
351
|
+
if (generic)
|
|
352
|
+
return generic;
|
|
353
|
+
if ((!idLocation || idLocation === 'body') && response.Body && typeof response.Body === 'object' && !Array.isArray(response.Body)) {
|
|
354
|
+
for (const [k, v] of Object.entries(response.Body)) {
|
|
355
|
+
if (/_id$/i.test(k) && (typeof v === 'string' || typeof v === 'number') && String(v).length > 0) {
|
|
356
|
+
return String(v);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* IDIOSYNCRATIC — async bulk-activity completion poll. The `POST /activities/*` create returned a job
|
|
364
|
+
* handle (`activity_id`); poll `GET /activities/{activity_id}` until the `state` leaves `processing`.
|
|
365
|
+
* `completed` → success (ExternalID = activity_id, via the loud-on-empty BuildCreatedResult); any other
|
|
366
|
+
* terminal state (`cancelled`/`failed`/`time_out`/`unknown`) or an exhausted poll budget → a loud failure
|
|
367
|
+
* that still carries the activity_id so a re-sync is idempotent rather than duplicating the job.
|
|
368
|
+
*/
|
|
369
|
+
async finishAsyncBulkActivity(objectName, obj, auth, baseURL, headers, createResponse) {
|
|
370
|
+
const activityID = this.ExtractIDFromResponse(createResponse, obj.CreateIDLocation);
|
|
371
|
+
if (!activityID) {
|
|
372
|
+
// No job handle on a 2xx is a FAILURE — BuildCreatedResult fails loudly on an empty id.
|
|
373
|
+
return this.BuildCreatedResult(activityID, createResponse.Status, objectName);
|
|
374
|
+
}
|
|
375
|
+
const maxAttempts = this.readConfigNumber(obj, 'bulkPollMaxAttempts') ?? CC_BULK_POLL_MAX_ATTEMPTS;
|
|
376
|
+
const intervalMs = this.readConfigNumber(obj, 'bulkPollIntervalMs') ?? CC_BULK_POLL_INTERVAL_MS;
|
|
377
|
+
const pollURL = this.joinURL(baseURL, `/activities/${encodeURIComponent(activityID)}`);
|
|
378
|
+
let state = CC_BULK_STATE_PROCESSING;
|
|
379
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
380
|
+
const pollResponse = await this.MakeHTTPRequest(auth, pollURL, 'GET', headers);
|
|
381
|
+
state = this.readActivityState(pollResponse);
|
|
382
|
+
if (state !== CC_BULK_STATE_PROCESSING)
|
|
383
|
+
break;
|
|
384
|
+
await this.sleep(intervalMs);
|
|
385
|
+
}
|
|
386
|
+
if (state === CC_BULK_STATE_COMPLETED) {
|
|
387
|
+
return this.BuildCreatedResult(activityID, createResponse.Status, objectName);
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
Success: false,
|
|
391
|
+
StatusCode: createResponse.Status,
|
|
392
|
+
ExternalID: activityID,
|
|
393
|
+
ErrorMessage: `Constant Contact bulk activity ${activityID} did not complete (final state: '${state}').`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* OVERRIDDEN so the generic Update/Delete/Get path can template Constant Contact's NAMED single-record
|
|
398
|
+
* path vars (`{segment_id}`, `{event_id}`, `{campaign_activity_id}`, …) — the base only substitutes
|
|
399
|
+
* `{id}`/`{ExternalID}`. A single-var path takes the whole ExternalID; a multi-var (nested) path takes the
|
|
400
|
+
* composite `parent|child` ExternalID split in path order.
|
|
401
|
+
*/
|
|
402
|
+
SubstituteIDInPath(path, externalID, idLocation) {
|
|
403
|
+
if (idLocation && idLocation !== 'path')
|
|
404
|
+
return path;
|
|
405
|
+
const vars = path.match(/\{\w+\}/g);
|
|
406
|
+
if (!vars || vars.length === 0)
|
|
407
|
+
return path;
|
|
408
|
+
if (vars.length === 1)
|
|
409
|
+
return path.replace(vars[0], encodeURIComponent(externalID));
|
|
410
|
+
const parts = externalID.split('|');
|
|
411
|
+
let out = path;
|
|
412
|
+
vars.forEach((v, i) => {
|
|
413
|
+
const val = parts[i] ?? parts[parts.length - 1] ?? externalID;
|
|
414
|
+
out = out.replace(v, encodeURIComponent(val));
|
|
415
|
+
});
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
// ── Connection test ────────────────────────────────────────────────────
|
|
419
|
+
/**
|
|
420
|
+
* Tests the connection by hitting the account-summary endpoint. A 2xx confirms the OAuth token is valid;
|
|
421
|
+
* 401/403 → auth failure; anything else → error.
|
|
422
|
+
*/
|
|
423
|
+
async TestConnection(companyIntegration, contextUser) {
|
|
424
|
+
try {
|
|
425
|
+
const auth = await this.Authenticate(companyIntegration, contextUser);
|
|
426
|
+
const baseURL = this.GetBaseURL(companyIntegration, auth);
|
|
427
|
+
const headers = this.BuildHeaders(auth);
|
|
428
|
+
const response = await this.MakeHTTPRequest(auth, `${baseURL}/account/summary`, 'GET', headers);
|
|
429
|
+
if (response.Status >= 200 && response.Status < 300) {
|
|
430
|
+
return { Success: true, Message: 'Constant Contact connection successful.', ServerVersion: 'Constant Contact V3' };
|
|
431
|
+
}
|
|
432
|
+
if (response.Status === 401 || response.Status === 403) {
|
|
433
|
+
return { Success: false, Message: `Constant Contact authentication failed (HTTP ${response.Status}). Check the OAuth client id/secret and refresh token.` };
|
|
434
|
+
}
|
|
435
|
+
return { Success: false, Message: `Constant Contact connection test returned HTTP ${response.Status}.` };
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
439
|
+
return { Success: false, Message: `Constant Contact connection test error: ${msg}` };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// ── Credential loading + rotating-refresh persistence ──────────────────
|
|
443
|
+
/** Reads the credential from the linked Credential entity (preferred) or the Configuration JSON fallback. */
|
|
444
|
+
async loadCredentials(companyIntegration, contextUser) {
|
|
445
|
+
let creds = null;
|
|
446
|
+
if (companyIntegration.CredentialID) {
|
|
447
|
+
creds = await this.loadFromCredentialEntity(companyIntegration.CredentialID, contextUser);
|
|
448
|
+
}
|
|
449
|
+
const configCreds = companyIntegration.Configuration ? this.parseCredentialJson(companyIntegration.Configuration) : null;
|
|
450
|
+
const merged = { ...(configCreds ?? {}), ...(creds ?? {}) };
|
|
451
|
+
if (!creds && !configCreds) {
|
|
452
|
+
throw new Error('No Constant Contact credential found. Attach a credential carrying ClientId + ClientSecret + ' +
|
|
453
|
+
'RefreshToken (OAuth2 Authorization-Code flow), or set Configuration JSON.');
|
|
454
|
+
}
|
|
455
|
+
return merged;
|
|
456
|
+
}
|
|
457
|
+
/** Loads a credential row and parses its Values JSON. */
|
|
458
|
+
async loadFromCredentialEntity(credentialID, contextUser, provider) {
|
|
459
|
+
try {
|
|
460
|
+
const md = provider ?? new Metadata();
|
|
461
|
+
const credential = await md.GetEntityObject('MJ: Credentials', contextUser);
|
|
462
|
+
const loaded = await credential.Load(credentialID);
|
|
463
|
+
if (!loaded || !credential.Values)
|
|
464
|
+
return null;
|
|
465
|
+
return this.parseCredentialJson(credential.Values);
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
// Best-effort: a credential-store read failure falls back to the Configuration JSON (loadCredentials
|
|
469
|
+
// still throws later if neither source supplies the client id/secret).
|
|
470
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
471
|
+
console.warn(`[constant-contact] Credential load failed for ${credentialID}: ${msg}`);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/** Extracts Constant Contact OAuth2 credential fields from a credential/config JSON string. */
|
|
476
|
+
parseCredentialJson(json) {
|
|
477
|
+
try {
|
|
478
|
+
const parsed = JSON.parse(json);
|
|
479
|
+
return {
|
|
480
|
+
ClientId: this.firstString(parsed, ['ClientId', 'clientId', 'client_id', 'apiKey', 'ApiKey', 'api_key']),
|
|
481
|
+
ClientSecret: this.firstString(parsed, ['ClientSecret', 'clientSecret', 'client_secret', 'appSecret', 'AppSecret', 'app_secret']),
|
|
482
|
+
RefreshToken: this.firstString(parsed, ['RefreshToken', 'refreshToken', 'refresh_token']),
|
|
483
|
+
AccessToken: this.firstString(parsed, ['AccessToken', 'accessToken', 'access_token']),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Persists the newly-rotated refresh token (and current access token) back to the linked Credential
|
|
492
|
+
* record so the next cold-start refresh uses the correct single-use token. Best-effort: a persistence
|
|
493
|
+
* failure is logged, never thrown (the in-memory token still works for the current run).
|
|
494
|
+
*/
|
|
495
|
+
async persistRotatedRefreshToken(companyIntegration, contextUser, refreshToken, accessToken, provider) {
|
|
496
|
+
const credentialID = companyIntegration.CredentialID;
|
|
497
|
+
if (!credentialID)
|
|
498
|
+
return;
|
|
499
|
+
try {
|
|
500
|
+
const md = provider ?? new Metadata();
|
|
501
|
+
const credential = await md.GetEntityObject('MJ: Credentials', contextUser);
|
|
502
|
+
const loaded = await credential.Load(credentialID);
|
|
503
|
+
if (!loaded || !credential.Values)
|
|
504
|
+
return;
|
|
505
|
+
const parsed = JSON.parse(credential.Values);
|
|
506
|
+
const updated = { ...parsed, RefreshToken: refreshToken, AccessToken: accessToken };
|
|
507
|
+
credential.Values = JSON.stringify(updated);
|
|
508
|
+
const saved = await credential.Save();
|
|
509
|
+
if (!saved) {
|
|
510
|
+
const detail = credential.LatestResult?.CompleteMessage ?? 'unknown';
|
|
511
|
+
console.warn(`[constant-contact] Failed to persist rotated refresh token: ${detail}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch (err) {
|
|
515
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
516
|
+
console.warn(`[constant-contact] Refresh-token persistence error: ${msg}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
520
|
+
/** Reads the opaque `cursor` query-param value from a `_links.next.href` (relative or absolute). */
|
|
521
|
+
extractCursorFromHref(href) {
|
|
522
|
+
const qIndex = href.indexOf('?');
|
|
523
|
+
if (qIndex < 0)
|
|
524
|
+
return null;
|
|
525
|
+
for (const pair of href.slice(qIndex + 1).split('&')) {
|
|
526
|
+
const eq = pair.indexOf('=');
|
|
527
|
+
if (eq < 0)
|
|
528
|
+
continue;
|
|
529
|
+
const key = decodeURIComponent(pair.slice(0, eq));
|
|
530
|
+
if (key.toLowerCase() === 'cursor') {
|
|
531
|
+
const val = decodeURIComponent(pair.slice(eq + 1));
|
|
532
|
+
return val.length > 0 ? val : null;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
/** True when the object is an async Bulk-Activity job endpoint (poll-for-completion write shape). */
|
|
538
|
+
isAsyncBulkActivity(obj) {
|
|
539
|
+
return this.readConfigString(obj, 'resourceTag') === CC_BULK_ACTIVITY_TAG;
|
|
540
|
+
}
|
|
541
|
+
/** Reads the async-job `state` from a `GET /activities/{activity_id}` poll response (else 'unknown'). */
|
|
542
|
+
readActivityState(response) {
|
|
543
|
+
const body = response.Body;
|
|
544
|
+
if (body && typeof body === 'object' && !Array.isArray(body)) {
|
|
545
|
+
const s = body.state;
|
|
546
|
+
if (typeof s === 'string' && s.trim().length > 0)
|
|
547
|
+
return s.trim();
|
|
548
|
+
}
|
|
549
|
+
return CC_BULK_STATE_UNKNOWN;
|
|
550
|
+
}
|
|
551
|
+
/** Poll delay — overridable in tests (a Mocked subclass returns immediately so the suite never blocks). */
|
|
552
|
+
async sleep(ms) {
|
|
553
|
+
if (ms <= 0)
|
|
554
|
+
return;
|
|
555
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
556
|
+
}
|
|
557
|
+
/** Substitutes CreateAPIPath parent template vars from the record's own attributes (nested creates). */
|
|
558
|
+
substitutePathVarsFromAttributes(path, attributes) {
|
|
559
|
+
return path.replace(/\{(\w+)\}/g, (match, name) => {
|
|
560
|
+
const v = attributes[name];
|
|
561
|
+
return v != null && String(v).length > 0 ? encodeURIComponent(String(v)) : match;
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
/** Computes the new watermark = max IncrementalWatermarkField value seen (never below the current). */
|
|
565
|
+
computeNewWatermark(obj, records, current) {
|
|
566
|
+
const wmField = obj.IncrementalWatermarkField;
|
|
567
|
+
if (!wmField)
|
|
568
|
+
return undefined;
|
|
569
|
+
let max;
|
|
570
|
+
for (const record of records) {
|
|
571
|
+
const value = record.Fields[wmField];
|
|
572
|
+
if (typeof value === 'string' && value.length > 0 && (!max || value > max))
|
|
573
|
+
max = value;
|
|
574
|
+
}
|
|
575
|
+
if (!max)
|
|
576
|
+
return current ?? undefined;
|
|
577
|
+
if (current && max <= current)
|
|
578
|
+
return current;
|
|
579
|
+
return max;
|
|
580
|
+
}
|
|
581
|
+
/** Reads an explicit full base-URL override from the connection Configuration (sandbox/test/mock only). */
|
|
582
|
+
resolveBaseURLOverride(companyIntegration) {
|
|
583
|
+
if (!companyIntegration.Configuration)
|
|
584
|
+
return null;
|
|
585
|
+
let parsed;
|
|
586
|
+
try {
|
|
587
|
+
parsed = JSON.parse(companyIntegration.Configuration);
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
const raw = this.firstString(parsed, ['BaseURL', 'BaseUrl', 'baseURL', 'baseUrl', 'APIBaseURL', 'ApiBaseURL', 'apiBaseUrl']);
|
|
593
|
+
if (raw && /^https?:\/\//i.test(raw.trim()))
|
|
594
|
+
return raw.trim();
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Optional OAuth2 token-endpoint override from the connection Configuration. Constant Contact's
|
|
599
|
+
* default authz host (`CC_TOKEN_URL`) differs from the API host, so this is read independently of
|
|
600
|
+
* the base-URL override. Lets a deployment target a non-default authz host and makes the connector
|
|
601
|
+
* testable against a mock token endpoint. Falls back to `CC_TOKEN_URL` when unset.
|
|
602
|
+
*/
|
|
603
|
+
resolveTokenURLOverride(companyIntegration) {
|
|
604
|
+
if (!companyIntegration.Configuration)
|
|
605
|
+
return null;
|
|
606
|
+
let parsed;
|
|
607
|
+
try {
|
|
608
|
+
parsed = JSON.parse(companyIntegration.Configuration);
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
const raw = this.firstString(parsed, ['TokenURL', 'TokenUrl', 'tokenURL', 'tokenUrl', 'TokenEndpoint', 'tokenEndpoint', 'token_url']);
|
|
614
|
+
if (raw && /^https?:\/\//i.test(raw.trim()))
|
|
615
|
+
return raw.trim();
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
/** Joins a base URL and a path (mirrors the base's private BuildFullURL). */
|
|
619
|
+
joinURL(baseURL, apiPath) {
|
|
620
|
+
const base = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
|
|
621
|
+
const path = apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
|
|
622
|
+
return `${base}${path}`;
|
|
623
|
+
}
|
|
624
|
+
/** Gets an IO from the cache without throwing (used by StableOrderingKey, which may be called early). */
|
|
625
|
+
tryGetCachedObject(objectName) {
|
|
626
|
+
try {
|
|
627
|
+
const integ = IntegrationEngineBase.Instance.GetIntegrationByName(this.IntegrationName);
|
|
628
|
+
if (!integ)
|
|
629
|
+
return null;
|
|
630
|
+
return IntegrationEngineBase.Instance.GetIntegrationObject(integ.ID, objectName) ?? null;
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/** Reads a trimmed string value from an IntegrationObject's Configuration JSON (tolerant of absent/invalid). */
|
|
637
|
+
readConfigString(obj, key) {
|
|
638
|
+
const raw = obj.Configuration;
|
|
639
|
+
if (!raw || typeof raw !== 'string')
|
|
640
|
+
return null;
|
|
641
|
+
try {
|
|
642
|
+
const cfg = JSON.parse(raw);
|
|
643
|
+
const v = cfg[key];
|
|
644
|
+
return typeof v === 'string' && v.trim().length > 0 ? v.trim() : null;
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/** Reads a finite number value from an IntegrationObject's Configuration JSON (tolerant of absent/invalid). */
|
|
651
|
+
readConfigNumber(obj, key) {
|
|
652
|
+
const raw = obj.Configuration;
|
|
653
|
+
if (!raw || typeof raw !== 'string')
|
|
654
|
+
return null;
|
|
655
|
+
try {
|
|
656
|
+
const cfg = JSON.parse(raw);
|
|
657
|
+
const v = cfg[key];
|
|
658
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
659
|
+
return v;
|
|
660
|
+
if (typeof v === 'string' && v.trim().length > 0) {
|
|
661
|
+
const n = Number(v);
|
|
662
|
+
return Number.isFinite(n) ? n : null;
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
/** Returns the first present, non-empty string value among the given keys. */
|
|
671
|
+
firstString(obj, keys) {
|
|
672
|
+
for (const k of keys) {
|
|
673
|
+
const v = obj[k];
|
|
674
|
+
if (typeof v === 'string' && v.length > 0)
|
|
675
|
+
return v;
|
|
676
|
+
}
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
ConstantContactConnector = __decorate([
|
|
681
|
+
RegisterClass(BaseIntegrationConnector, 'ConstantContactConnector')
|
|
682
|
+
], ConstantContactConnector);
|
|
683
|
+
export { ConstantContactConnector };
|
|
684
|
+
/** Tree-shaking prevention — import and call from the module entry point. */
|
|
685
|
+
export function LoadConstantContactConnector() { }
|
|
686
|
+
//# sourceMappingURL=ConstantContactConnector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConstantContactConnector.js","sourceRoot":"","sources":["../src/ConstantContactConnector.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAyC,MAAM,sBAAsB,CAAC;AAMvF,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,kBAAkB,GAarB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AA4DxF,2EAA2E;AAE3E,+EAA+E;AAC/E,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAC9C,uDAAuD;AACvD,MAAM,YAAY,GAAG,2DAA2D,CAAC;AACjF,8GAA8G;AAC9G,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAC1C,kDAAkD;AAClD,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAC1C,8GAA8G;AAC9G,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,6EAA6E;AAC7E,oGAAoG;AACpG,8FAA8F;AAC9F,yGAAyG;AAEzG,6EAA6E;AAC7E,MAAM,wBAAwB,GAAG,YAAY,CAAC;AAC9C,kCAAkC;AAClC,MAAM,uBAAuB,GAAG,WAAW,CAAC;AAC5C,mEAAmE;AACnE,MAAM,qBAAqB,GAAG,SAAS,CAAC;AACxC,6FAA6F;AAC7F,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAC/C,uGAAuG;AACvG,MAAM,yBAAyB,GAAG,EAAE,CAAC;AACrC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAEvC,6EAA6E;AAE7E;;;;;;;;GAQG;AAEI,IAAM,wBAAwB,GAA9B,MAAM,wBAAyB,SAAQ,4BAA4B;IAAnE;;QAEH,yGAAyG;QACxF,iBAAY,GAAG,IAAI,kBAAkB,EAAE,CAAC;QACzD,6EAA6E;QACrE,eAAU,GAAyB,IAAI,CAAC;QAGhD,+FAA+F;QACvF,qBAAgB,GAAkB,IAAI,CAAC;IA6pBnD,CAAC;IA3pBG,wEAAwE;IAExE,mHAAmH;IACnH,IAAoB,eAAe;QAC/B,OAAO,kBAAkB,CAAC;IAC9B,CAAC;IAED,+EAA+E;IAE/E,IAAoB,cAAc,KAAc,OAAO,IAAI,CAAC,CAAC,CAAC;IAC9D,IAAoB,cAAc,KAAc,OAAO,IAAI,CAAC,CAAC,CAAC;IAC9D,IAAoB,cAAc,KAAc,OAAO,IAAI,CAAC,CAAC,CAAC;IAE9D;;;;;;;OAOG;IACH,IAAoB,wBAAwB;QACxC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,4FAA4F;IAE5F;;;;OAIG;IACH,IAAoB,eAAe;QAC/B,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACzC,CAAC;IAED,kGAAkG;IAClG,IAAoB,kBAAkB,KAAoB,OAAO,CAAC,CAAC,CAAC,CAAC;IAErE;;;;OAIG;IACa,iBAAiB,CAAC,UAAkB;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,MAAM,QAAQ,GAAI,GAAwD,CAAC,iBAAiB,CAAC;QAC7F,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnE,MAAM,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QAClE,OAAO,EAAE,EAAE,IAAI,IAAI,IAAI,CAAC;IAC5B,CAAC;IAED;;;;;;;OAOG;IACa,KAAK,CAAC,gBAAgB,CAClC,kBAA8C,EAC9C,WAAqB;QAErB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAC;QAC3E,MAAM,OAAO,CAAC,GAAG,CACb,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC3B,IAAI,CAAC;gBACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,kBAAkB,EAAE,GAAG,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;gBACrG,GAAG,CAAC,MAAM,GAAG,8BAA8B,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACrE,CAAC;YAAC,MAAM,CAAC;gBACL,qEAAqE;YACzE,CAAC;QACL,CAAC,CAAC,CACL,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,wEAAwE;IAExE;;;;;OAKG;IACgB,KAAK,CAAC,YAAY,CACjC,kBAA8C,EAC9C,WAAqB;QAErB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,0BAA0B,EAAE,CAAC;YACnG,OAAO,IAAI,CAAC,UAAU,CAAC;QAC3B,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAC;QAC1E,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CAAC,iFAAiF,CAAC,CAAC;QACvG,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,mBAAmB;YAAE,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAAC;QAC7E,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,qGAAqG,CAAC,CAAC;QAC3H,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAChD;YACI,QAAQ,EAAE,IAAI,CAAC,uBAAuB,CAAC,kBAAkB,CAAC,IAAI,YAAY;YAC1E,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,YAAY,EAAE,IAAI,CAAC,mBAAmB;YACtC,YAAY,EAAE,IAAI;SACrB,EACD,eAAe,CAClB,CAAC;QAEF,0EAA0E;QAC1E,IAAI,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACxE,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC;YACnC,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC;YACnC,MAAM,IAAI,CAAC,0BAA0B,CAAC,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;QACvG,CAAC;QAED,IAAI,CAAC,UAAU,GAAG;YACd,KAAK,EAAE,KAAK,CAAC,WAAW;YACxB,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;YACpC,eAAe,EAAE,IAAI,CAAC,sBAAsB,CAAC,kBAAkB,CAAC,IAAI,SAAS;SAChF,CAAC;QACF,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED,qDAAqD;IAClC,YAAY,CAAC,IAAqB;QACjD,OAAO;YACH,eAAe,EAAE,UAAW,IAAsB,CAAC,KAAK,EAAE;YAC1D,QAAQ,EAAE,kBAAkB;YAC5B,cAAc,EAAE,kBAAkB;SACrC,CAAC;IACN,CAAC;IAED,yGAAyG;IACtF,KAAK,CAAC,eAAe,CACpC,KAAsB,EACtB,GAAW,EACX,MAAc,EACd,OAA+B,EAC/B,IAAc;QAEd,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC9B,MAAM;YACN,OAAO;YACP,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;SAC9D,CAAC,CAAC;QACH,MAAM,WAAW,GAA2B,EAAE,CAAC;QAC/C,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1E,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,MAAM,GAAY,IAAI,CAAC;QAC3B,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC;gBAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,MAAM,GAAG,IAAI,CAAC;YAAC,CAAC;QAC/D,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAC3E,CAAC;IAED;;;;OAIG;IACgB,iBAAiB,CAAC,OAAgB,EAAE,eAA8B;QACjF,IAAI,OAAO,IAAI,IAAI;YAAE,OAAO,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,OAAO,OAAoC,CAAC;QACxE,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,OAAkC,CAAC;QAChD,IAAI,eAAe,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC,eAAe,CAA8B,CAAC;QAC9D,CAAC;QACD,4FAA4F;QAC5F,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACgB,qBAAqB,CACpC,OAAgB,EAChB,cAA8B,EAC9B,YAAoB,EACpB,cAAsB,EACtB,SAAiB;QAEjB,IAAI,cAAc,KAAK,MAAM;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACzD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACvE,MAAM,IAAI,GAAI,OAA2B,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;QAC7D,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC7E,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IACjD,CAAC;IAED;;;;OAIG;IACgB,UAAU,CAAC,mBAA+C,EAAE,IAAqB;QAChG,MAAM,GAAG,GAAG,IAAqB,CAAC;QAClC,IAAI,GAAG,CAAC,eAAe;YAAE,OAAO,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACxE,OAAO,WAAW,CAAC;IACvB,CAAC;IAED;;;OAGG;IACgB,iBAAiB,CAChC,QAAgB,EAChB,GAA8B,EAC9B,KAAa,EACb,OAAe,EACf,MAAe,EACf,iBAA0B;QAE1B,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACrD,IAAI,MAAM;YAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,UAAU,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QACjF,MAAM,QAAQ,GAAG,iBAAiB,IAAI,GAAG,CAAC,eAAe,IAAI,oBAAoB,CAAC;QAClF,OAAO,GAAG,QAAQ,GAAG,SAAS,SAAS,QAAQ,EAAE,CAAC;IACtD,CAAC;IAED;;;;;OAKG;IACgB,wBAAwB,CAAC,GAAW,EAAE,GAA8B;QACnF,IAAI,MAAM,GAAG,KAAK,CAAC,wBAAwB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,gBAAgB,IAAI,GAAG,CAAC,uBAAuB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACtF,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;YAC3E,IAAI,YAAY,IAAI,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,kBAAkB,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;gBACzF,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBACnD,MAAM,GAAG,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,EAAE,CAAC;YAC7C,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,yEAAyE;IAEzE;;;;;OAKG;IACa,KAAK,CAAC,YAAY,CAAC,GAAiB;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,kBAAkB,CAAC,aAAa,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;QACvF,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;YAC7C,IAAI,GAAG,CAAC,uBAAuB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACjD,MAAM,YAAY,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;gBACvF,IAAI,YAAY;oBAAE,MAAM,CAAC,iBAAiB,GAAG,YAAY,CAAC;YAC9D,CAAC;YACD,OAAO,MAAM,CAAC;QAClB,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QACjC,CAAC;IACL,CAAC;IAED,yEAAyE;IACzE,EAAE;IACF,sGAAsG;IACtG,sGAAsG;IACtG,2GAA2G;IAC3G,8GAA8G;IAC9G,kGAAkG;IAElG;;;;;;;;;;OAUG;IACa,KAAK,CAAC,YAAY,CAAC,GAAwB;QACvD,MAAM,EAAE,GAAG,GAAG,CAAC,kBAAgD,CAAC;QAChE,MAAM,WAAW,GAAG,GAAG,CAAC,WAAuB,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;QACnE,MAAM,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;QAClD,gGAAgG;QAChG,IAAI,CAAC,CAAC,aAAa,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC9E,OAAO,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,oFAAoF;QACpF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,gCAAgC,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;QACtF,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,eAAe,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;QAC7F,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACxF,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YAClD,OAAO;gBACH,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,QAAQ,CAAC,MAAM;gBAC3B,YAAY,EAAE,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,IAAI,QAAQ,QAAQ,CAAC,MAAM,YAAY;aAC1F,CAAC;QACN,CAAC;QACD,IAAI,WAAW,EAAE,CAAC;YACd,OAAO,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC/F,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAChF,CAAC;IAED;;;;;OAKG;IACgB,qBAAqB,CAAC,QAAsB,EAAE,UAAyB;QACtF,MAAM,OAAO,GAAG,KAAK,CAAC,qBAAqB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClE,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAC5B,IAAI,CAAC,CAAC,UAAU,IAAI,UAAU,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,IAAI,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAChI,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAA+B,CAAC,EAAE,CAAC;gBAC5E,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9F,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;gBACrB,CAAC;YACL,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,uBAAuB,CACjC,UAAkB,EAClB,GAA8B,EAC9B,IAAqB,EACrB,OAAe,EACf,OAA+B,EAC/B,cAA4B;QAE5B,MAAM,UAAU,GAAG,IAAI,CAAC,qBAAqB,CAAC,cAAc,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACpF,IAAI,CAAC,UAAU,EAAE,CAAC;YACd,wFAAwF;YACxF,OAAO,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,qBAAqB,CAAC,IAAI,yBAAyB,CAAC;QACnG,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,oBAAoB,CAAC,IAAI,wBAAwB,CAAC;QAChG,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,eAAe,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvF,IAAI,KAAK,GAAG,wBAAwB,CAAC;QACrC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YACrD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YAC/E,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC;YAC7C,IAAI,KAAK,KAAK,wBAAwB;gBAAE,MAAM;YAC9C,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,KAAK,KAAK,uBAAuB,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAClF,CAAC;QACD,OAAO;YACH,OAAO,EAAE,KAAK;YACd,UAAU,EAAE,cAAc,CAAC,MAAM;YACjC,UAAU,EAAE,UAAU;YACtB,YAAY,EAAE,kCAAkC,UAAU,oCAAoC,KAAK,KAAK;SAC3G,CAAC;IACN,CAAC;IAED;;;;;OAKG;IACgB,kBAAkB,CAAC,IAAY,EAAE,UAAkB,EAAE,UAAyB;QAC7F,IAAI,UAAU,IAAI,UAAU,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC;QACpF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,GAAG,GAAG,IAAI,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAClB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,UAAU,CAAC;YAC9D,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC;IACf,CAAC;IAED,0EAA0E;IAE1E;;;OAGG;IACa,KAAK,CAAC,cAAc,CAChC,kBAA8C,EAC9C,WAAqB;QAErB,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAC;YACtE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;YAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACxC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,GAAG,OAAO,kBAAkB,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YAChG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,yCAAyC,EAAE,aAAa,EAAE,qBAAqB,EAAE,CAAC;YACvH,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACrD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,gDAAgD,QAAQ,CAAC,MAAM,wDAAwD,EAAE,CAAC;YAChK,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,kDAAkD,QAAQ,CAAC,MAAM,GAAG,EAAE,CAAC;QAC7G,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,2CAA2C,GAAG,EAAE,EAAE,CAAC;QACzF,CAAC;IACL,CAAC;IAED,0EAA0E;IAE1E,6GAA6G;IACrG,KAAK,CAAC,eAAe,CACzB,kBAA8C,EAC9C,WAAqB;QAErB,IAAI,KAAK,GAAsC,IAAI,CAAC;QACpD,IAAI,kBAAkB,CAAC,YAAY,EAAE,CAAC;YAClC,KAAK,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,kBAAkB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QAC9F,CAAC;QACD,MAAM,WAAW,GAAG,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACzH,MAAM,MAAM,GAA+B,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACxF,IAAI,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACX,+FAA+F;gBAC/F,2EAA2E,CAC9E,CAAC;QACN,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,yDAAyD;IACjD,KAAK,CAAC,wBAAwB,CAClC,YAAoB,EACpB,WAAqB,EACrB,QAA4B;QAE5B,IAAI,CAAC;YACD,MAAM,EAAE,GAAG,QAAQ,IAAI,IAAI,QAAQ,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,eAAe,CAAqB,iBAAiB,EAAE,WAAW,CAAC,CAAC;YAChG,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACnD,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YAC/C,OAAO,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,qGAAqG;YACrG,uEAAuE;YACvE,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,IAAI,CAAC,iDAAiD,YAAY,KAAK,GAAG,EAAE,CAAC,CAAC;YACtF,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,+FAA+F;IACvF,mBAAmB,CAAC,IAAY;QACpC,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;YAC3D,OAAO;gBACH,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;gBACxG,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;gBACjI,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;gBACzF,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC;aACxF,CAAC;QACN,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;;;OAIG;IACO,KAAK,CAAC,0BAA0B,CACtC,kBAA8C,EAC9C,WAAqB,EACrB,YAAoB,EACpB,WAAmB,EACnB,QAA4B;QAE5B,MAAM,YAAY,GAAG,kBAAkB,CAAC,YAAY,CAAC;QACrD,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,IAAI,CAAC;YACD,MAAM,EAAE,GAAG,QAAQ,IAAI,IAAI,QAAQ,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,eAAe,CAAqB,iBAAiB,EAAE,WAAW,CAAC,CAAC;YAChG,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACnD,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM;gBAAE,OAAO;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAA4B,CAAC;YACxE,MAAM,OAAO,GAA4B,EAAE,GAAG,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;YAC7G,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;YACtC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,MAAM,MAAM,GAAG,UAAU,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,CAAC;gBACrE,OAAO,CAAC,IAAI,CAAC,+DAA+D,MAAM,EAAE,CAAC,CAAC;YAC1F,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,IAAI,CAAC,uDAAuD,GAAG,EAAE,CAAC,CAAC;QAC/E,CAAC;IACL,CAAC;IAED,0EAA0E;IAE1E,oGAAoG;IAC5F,qBAAqB,CAAC,IAAY;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,EAAE,GAAG,CAAC;gBAAE,SAAS;YACrB,MAAM,GAAG,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAClD,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACjC,MAAM,GAAG,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBACnD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YACvC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,qGAAqG;IAC7F,mBAAmB,CAAC,GAA8B;QACtD,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,aAAa,CAAC,KAAK,oBAAoB,CAAC;IAC9E,CAAC;IAED,yGAAyG;IACjG,iBAAiB,CAAC,QAAsB;QAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC3B,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3D,MAAM,CAAC,GAAI,IAAgC,CAAC,KAAK,CAAC;YAClD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACtE,CAAC;QACD,OAAO,qBAAqB,CAAC;IACjC,CAAC;IAED,2GAA2G;IACjG,KAAK,CAAC,KAAK,CAAC,EAAU;QAC5B,IAAI,EAAE,IAAI,CAAC;YAAE,OAAO;QACpB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,wGAAwG;IAChG,gCAAgC,CAAC,IAAY,EAAE,UAAmC;QACtF,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;YACtD,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACrF,CAAC,CAAC,CAAC;IACP,CAAC;IAED,uGAAuG;IAC/F,mBAAmB,CACvB,GAA8B,EAC9B,OAAyB,EACzB,OAAsB;QAEtB,MAAM,OAAO,GAAG,GAAG,CAAC,yBAAyB,CAAC;QAC9C,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC/B,IAAI,GAAuB,CAAC;QAC5B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,GAAG,GAAG,CAAC;gBAAE,GAAG,GAAG,KAAK,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC,GAAG;YAAE,OAAO,OAAO,IAAI,SAAS,CAAC;QACtC,IAAI,OAAO,IAAI,GAAG,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAC9C,OAAO,GAAG,CAAC;IACf,CAAC;IAED,2GAA2G;IACnG,sBAAsB,CAAC,kBAA8C;QACzE,IAAI,CAAC,kBAAkB,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC;QACnD,IAAI,MAA+B,CAAC;QACpC,IAAI,CAAC;YAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,aAAa,CAA4B,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QAChH,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;QAC7H,IAAI,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAAE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;;OAKG;IACK,uBAAuB,CAAC,kBAA8C;QAC1E,IAAI,CAAC,kBAAkB,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC;QACnD,IAAI,MAA+B,CAAC;QACpC,IAAI,CAAC;YAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,aAAa,CAA4B,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QAChH,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC;QACtI,IAAI,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAAE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,6EAA6E;IACrE,OAAO,CAAC,OAAe,EAAE,OAAe;QAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACpE,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;QAC/D,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED,yGAAyG;IACjG,kBAAkB,CAAC,UAAkB;QACzC,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,qBAAqB,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACxF,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YACxB,OAAO,qBAAqB,CAAC,QAAQ,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,IAAI,CAAC;QAC7F,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,gHAAgH;IACxG,gBAAgB,CAAC,GAA8B,EAAE,GAAW;QAChE,MAAM,GAAG,GAAI,GAAoD,CAAC,aAAa,CAAC;QAChF,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACjD,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;YACvD,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,+GAA+G;IACvG,gBAAgB,CAAC,GAA8B,EAAE,GAAW;QAChE,MAAM,GAAG,GAAI,GAAoD,CAAC,aAAa,CAAC;QAChF,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACjD,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;YACvD,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACnB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAC;YAC1D,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/C,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;gBACpB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACzC,CAAC;YACD,OAAO,IAAI,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,8EAA8E;IACtE,WAAW,CAAC,GAA4B,EAAE,IAAc;QAC5D,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACjB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ,CAAA;AAtqBY,wBAAwB;IADpC,aAAa,CAAC,wBAAwB,EAAE,0BAA0B,CAAC;GACvD,wBAAwB,CAsqBpC;;AAED,6EAA6E;AAC7E,MAAM,UAAU,4BAA4B,KAAuB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from './ConstantContactConnector.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 './ConstantContactConnector.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,+BAA+B,CAAC;AAE9C;oGACoG;AACpG,MAAM,UAAU,iBAAiB,KAAiD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memberjunction/connector-constant-contact",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "MemberJunction ConstantContact connector.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"/dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc && tsc-alias -f",
|
|
14
|
+
"test": "vitest run --passWithNoTests"
|
|
15
|
+
},
|
|
16
|
+
"author": "MemberJunction.com",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@memberjunction/core": ">=5.42.0 <6.0.0",
|
|
20
|
+
"@memberjunction/core-entities": ">=5.42.0 <6.0.0",
|
|
21
|
+
"@memberjunction/global": ">=5.42.0 <6.0.0",
|
|
22
|
+
"@memberjunction/integration-engine": ">=5.42.0 <6.0.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@memberjunction/connector-schema-merge": "^1.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "24.10.11",
|
|
29
|
+
"tsc-alias": "^1.8.16",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^4.0.18",
|
|
32
|
+
"@memberjunction/core": "^5.42.0",
|
|
33
|
+
"@memberjunction/core-entities": "^5.42.0",
|
|
34
|
+
"@memberjunction/global": "^5.42.0",
|
|
35
|
+
"@memberjunction/integration-engine": "^5.42.0",
|
|
36
|
+
"@memberjunction/connector-schema-merge": "^1.0.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/MemberJunction/Integrations"
|
|
41
|
+
}
|
|
42
|
+
}
|