@mparticle/web-sdk 2.65.0 → 2.67.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/mparticle.common.js +119 -78
- package/dist/mparticle.esm.js +119 -78
- package/dist/mparticle.js +383 -128
- package/dist/types/src/identity/search.d.ts +64 -0
- package/dist/types/src/identity-utils.d.ts +17 -1
- package/dist/types/src/identity.interfaces.d.ts +37 -1
- package/dist/types/src/public-types.d.ts +1 -1
- package/dist/types/src/roktManager.d.ts +4 -0
- package/dist/types/src/stub/mparticle.stub.d.ts +1 -0
- package/package.json +1 -1
- package/src/identity/search.ts +233 -0
- package/src/identity-utils.ts +81 -1
- package/src/identity.interfaces.ts +46 -0
- package/src/identity.js +27 -0
- package/src/mparticle-instance-manager.ts +7 -0
- package/src/public-types.ts +4 -0
- package/src/roktManager.ts +35 -0
- package/src/stub/mparticle.stub.js +1 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Dictionary } from './utils';
|
|
1
|
+
import { Dictionary, Environment } from './utils';
|
|
2
2
|
import { BaseVault } from './vault';
|
|
3
3
|
import { IdentityApiData, UserIdentities, IdentityCallback } from '@mparticle/web-sdk';
|
|
4
4
|
import { IdentityAPIMethod } from './identity.interfaces';
|
|
5
5
|
import { IIdentityResponse, IMParticleUser } from './identity-user-interfaces';
|
|
6
6
|
import { IStore } from './store';
|
|
7
|
+
import type { IMParticleWebSDKInstance } from './mp-instance';
|
|
8
|
+
import { IIdentitySearchRequestBody, IdentitySearchCallback } from './identity/search';
|
|
7
9
|
export declare const CACHE_HEADER: "x-mp-max-age";
|
|
8
10
|
export type IdentityCache = BaseVault<Dictionary<ICachedIdentityCall>>;
|
|
9
11
|
export type IParseCachedIdentityResponse = (cachedIdentity: IIdentityResponse, mpid: string, callback: IdentityCallback, identityApiData: IdentityApiData, identityMethod: string, knownIdentities: IKnownIdentities, fromCachedIdentity: boolean) => void;
|
|
@@ -34,3 +36,17 @@ export declare const hasIdentityRequestChanged: (currentUser: IMParticleUser, ne
|
|
|
34
36
|
* @returns true if deviceId or other identifiers were explicitly provided in config, false otherwise
|
|
35
37
|
*/
|
|
36
38
|
export declare const hasExplicitIdentifier: (store: IStore | undefined | null) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Builds the /v1/identify-style envelope (client_sdk, environment,
|
|
41
|
+
* request_id, request_timestamp_ms) used to correlate IDSync requests
|
|
42
|
+
* across endpoints. `known_identities` is omitted so the caller can
|
|
43
|
+
* fold in the search-specific identifiers alongside the envelope.
|
|
44
|
+
*/
|
|
45
|
+
export declare const buildIdentitySearchEnvelope: (environment: Environment) => Omit<IIdentitySearchRequestBody, 'known_identities'>;
|
|
46
|
+
/**
|
|
47
|
+
* Wires the SDK instance into `sendSearchRequest`: gates on `canLog`,
|
|
48
|
+
* builds the `/v1/search` URL and request envelope, and dispatches.
|
|
49
|
+
* Lives here so the SDK glue (URL building, opt-out gate, dispatcher
|
|
50
|
+
* plumbing) is type-checked instead of being expressed in plain JS.
|
|
51
|
+
*/
|
|
52
|
+
export declare const executeSearchRequest: (mpInstance: IMParticleWebSDKInstance, workspaceApiKey: string, knownIdentities: UserIdentities, callback: IdentitySearchCallback) => void;
|
|
@@ -5,6 +5,7 @@ import { BaseVault } from './vault';
|
|
|
5
5
|
import { Dictionary, Environment, valueof } from './utils';
|
|
6
6
|
import Constants from './constants';
|
|
7
7
|
import { IdentityCallback, IUserAttributeChangeEvent, IUserIdentityChangeEvent, IMParticleUser, mParticleUserCart, IIdentityResponse } from './identity-user-interfaces';
|
|
8
|
+
import { IdentitySearchCallback } from './identity/search';
|
|
8
9
|
declare const platform: "web", sdkVendor: "mparticle", sdkVersion: string, HTTPCodes: {
|
|
9
10
|
readonly noHttpCoverage: -1;
|
|
10
11
|
readonly activeIdentityRequest: -2;
|
|
@@ -97,6 +98,42 @@ export interface SDKIdentityApi {
|
|
|
97
98
|
getUsers?(): IMParticleUser[];
|
|
98
99
|
aliasUsers?(aliasRequest?: IAliasRequest, callback?: IdentityCallback): void;
|
|
99
100
|
createAliasRequest?(sourceUser: IMParticleUser, destinationUser: IMParticleUser, scope?: AliasRequestScope): IAliasRequest;
|
|
101
|
+
/**
|
|
102
|
+
* Sends a request to mParticle's IDSync `/v1/search` endpoint to look up
|
|
103
|
+
* a workspace identity without affecting the current user. The callback
|
|
104
|
+
* receives `httpCode` (always) and an optional `body` containing the
|
|
105
|
+
* parsed JSON response. Consumers should gate behaviour on
|
|
106
|
+
* `httpCode === 200`.
|
|
107
|
+
*
|
|
108
|
+
* `workspaceApiKey` is a workspace-specific API key supplied by the
|
|
109
|
+
* caller (from a kit's settings). It is sent as the `x-mp-key` header.
|
|
110
|
+
* The SDK's own workspace token is intentionally not used.
|
|
111
|
+
*/
|
|
112
|
+
search?(workspaceApiKey: string, knownIdentities: UserIdentities, callback: IdentitySearchCallback): void;
|
|
113
|
+
}
|
|
114
|
+
export type { IIdentitySearchResult, IIdentitySearchResponseBody, IdentitySearchCallback, } from './identity/search';
|
|
115
|
+
export interface IUserIdentities {
|
|
116
|
+
customerid?: string;
|
|
117
|
+
email?: string;
|
|
118
|
+
other?: string;
|
|
119
|
+
other2?: string;
|
|
120
|
+
other3?: string;
|
|
121
|
+
other4?: string;
|
|
122
|
+
other5?: string;
|
|
123
|
+
other6?: string;
|
|
124
|
+
other7?: string;
|
|
125
|
+
other8?: string;
|
|
126
|
+
other9?: string;
|
|
127
|
+
other10?: string;
|
|
128
|
+
mobile_number?: string;
|
|
129
|
+
phone_number_2?: string;
|
|
130
|
+
phone_number_3?: string;
|
|
131
|
+
facebook?: string;
|
|
132
|
+
facebookcustomaudienceid?: string;
|
|
133
|
+
google?: string;
|
|
134
|
+
twitter?: string;
|
|
135
|
+
microsoft?: string;
|
|
136
|
+
yahoo?: string;
|
|
100
137
|
}
|
|
101
138
|
export interface IIdentity {
|
|
102
139
|
audienceManager: AudienceManager;
|
|
@@ -114,4 +151,3 @@ export interface IIdentity {
|
|
|
114
151
|
*/
|
|
115
152
|
mParticleUserCart(): mParticleUserCart;
|
|
116
153
|
}
|
|
117
|
-
export {};
|
|
@@ -16,7 +16,7 @@ export type { Batch, } from '@mparticle/event-models';
|
|
|
16
16
|
export type { EventType, CommerceEventType, IdentityType, ProductActionType, PromotionActionType, MessageType, } from './types';
|
|
17
17
|
export type { SDKInitConfig, DataPlanConfig, BaseEvent, SDKEventCustomFlags, LogLevelType, MParticleWebSDK, } from './sdkRuntimeModels';
|
|
18
18
|
export type { IMParticleUser, IdentityCallback, IdentityResult, IdentityResultBody, IdentityModifyResultBody, ISDKUserIdentity, ISDKUserAttributes, } from './identity-user-interfaces';
|
|
19
|
-
export type { SDKIdentityApi, IAliasRequest, IAliasCallback, IAliasResult, SDKIdentityTypeEnum, } from './identity.interfaces';
|
|
19
|
+
export type { SDKIdentityApi, IAliasRequest, IAliasCallback, IAliasResult, SDKIdentityTypeEnum, IIdentitySearchResult, IIdentitySearchResponseBody, IdentitySearchCallback, IUserIdentities } from './identity.interfaces';
|
|
20
20
|
export type { SDKECommerceAPI, SDKCart, } from './ecommerce.interfaces';
|
|
21
21
|
export type { SDKProduct, SDKPromotion, SDKImpression, SDKProductImpression, } from './sdkRuntimeModels';
|
|
22
22
|
export type { SDKConsentApi, SDKConsentState, SDKConsentStateData, SDKGDPRConsentState, SDKCCPAConsentState, } from './consent';
|
|
@@ -54,6 +54,7 @@ export interface IRoktKit {
|
|
|
54
54
|
selectPlacements: (options: IRoktSelectPlacementsOptions) => Promise<IRoktSelection>;
|
|
55
55
|
setExtensionData<T>(extensionData: IRoktPartnerExtensionData<T>): void;
|
|
56
56
|
use: <T>(name: string) => Promise<T>;
|
|
57
|
+
onShoppableAdsReady(callback: () => void): void;
|
|
57
58
|
launcherOptions?: Dictionary<any>;
|
|
58
59
|
settings?: IRoktKitSettings;
|
|
59
60
|
integrationName?: string;
|
|
@@ -82,6 +83,7 @@ export default class RoktManager {
|
|
|
82
83
|
private captureTiming?;
|
|
83
84
|
private onReadyCallback;
|
|
84
85
|
private initialized;
|
|
86
|
+
private isShoppableAdsLoaded;
|
|
85
87
|
/**
|
|
86
88
|
* Sets a callback to be invoked when RoktManager becomes ready
|
|
87
89
|
*/
|
|
@@ -131,6 +133,8 @@ export default class RoktManager {
|
|
|
131
133
|
hashAttributes(attributes: RoktAttributes): Promise<RoktAttributes>;
|
|
132
134
|
setExtensionData<T>(extensionData: IRoktPartnerExtensionData<T>): void;
|
|
133
135
|
use<T>(name: string): Promise<T>;
|
|
136
|
+
onShoppableAdsReady(callback: () => void): void;
|
|
137
|
+
flushOnShoppableAdsReadyMessageQueue(kit: IRoktKit): void;
|
|
134
138
|
/**
|
|
135
139
|
* Hashes an attribute using SHA-256
|
|
136
140
|
*
|
package/package.json
CHANGED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import Constants, { HTTP_OK, HTTP_NOT_FOUND } from '../constants';
|
|
2
|
+
import { SDKLoggerApi } from '../sdkRuntimeModels';
|
|
3
|
+
import { Environment, isFunction } from '../utils';
|
|
4
|
+
import {
|
|
5
|
+
AsyncUploader,
|
|
6
|
+
FetchUploader,
|
|
7
|
+
IFetchPayload,
|
|
8
|
+
XHRUploader,
|
|
9
|
+
} from '../uploaders';
|
|
10
|
+
import {
|
|
11
|
+
ErrorCodes,
|
|
12
|
+
IErrorReportingService,
|
|
13
|
+
WSDKErrorSeverity,
|
|
14
|
+
} from '../reporting/types';
|
|
15
|
+
import { IdentityApiData, UserIdentities } from '@mparticle/web-sdk';
|
|
16
|
+
import Validators from '../validators';
|
|
17
|
+
|
|
18
|
+
const { HTTPCodes } = Constants;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Body payload returned by the `/v1/search` endpoint, as parsed JSON.
|
|
22
|
+
*
|
|
23
|
+
* The shape mirrors `/v1/identify` responses. All fields are optional because
|
|
24
|
+
* non-200 responses (e.g. 404 NOT_FOUND_ERROR) may include partial or
|
|
25
|
+
* error-shaped bodies, and the consumer should only rely on body fields when
|
|
26
|
+
* `httpCode === 200`.
|
|
27
|
+
*/
|
|
28
|
+
export interface IIdentitySearchResponseBody {
|
|
29
|
+
context?: string | null;
|
|
30
|
+
mpid?: string;
|
|
31
|
+
matched_identities?: Record<string, string>;
|
|
32
|
+
is_ephemeral?: boolean;
|
|
33
|
+
is_logged_in?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Result delivered to the consumer's callback. `httpCode` is always present;
|
|
38
|
+
* `body` is present whenever the response had a parseable JSON body.
|
|
39
|
+
*
|
|
40
|
+
* For non-network errors (missing API key, validation failures, JSON parse
|
|
41
|
+
* errors) `httpCode` will be `HTTPCodes.noHttpCoverage` (-1) and `body` will
|
|
42
|
+
* be omitted. The consumer is expected to gate behaviour on
|
|
43
|
+
* `httpCode === 200`.
|
|
44
|
+
*/
|
|
45
|
+
export interface IIdentitySearchResult {
|
|
46
|
+
httpCode: number;
|
|
47
|
+
body?: IIdentitySearchResponseBody;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type IdentitySearchCallback = (result: IIdentitySearchResult) => void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Body posted to `/v1/search`. Mirrors the `/v1/identify` request envelope so
|
|
54
|
+
* that the IDSync service can correlate requests across endpoints.
|
|
55
|
+
*/
|
|
56
|
+
export interface IIdentitySearchRequestBody {
|
|
57
|
+
client_sdk: {
|
|
58
|
+
platform: string;
|
|
59
|
+
sdk_vendor: string;
|
|
60
|
+
sdk_version: string;
|
|
61
|
+
};
|
|
62
|
+
environment: Environment;
|
|
63
|
+
request_id: string;
|
|
64
|
+
request_timestamp_ms: number;
|
|
65
|
+
known_identities: UserIdentities;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface IIdentitySearchPayload extends IFetchPayload {
|
|
69
|
+
headers: {
|
|
70
|
+
Accept: string;
|
|
71
|
+
'Content-Type': string;
|
|
72
|
+
'x-mp-key': string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Sends a POST to mParticle's IDSync Search endpoint and invokes `callback`
|
|
78
|
+
* with the HTTP status and parsed body.
|
|
79
|
+
*
|
|
80
|
+
* Defensive contract:
|
|
81
|
+
* - No identifier with a non-empty string value -> callback with
|
|
82
|
+
* `{ httpCode: noHttpCoverage }`, no network call.
|
|
83
|
+
* - Missing `apiKey` -> callback with `{ httpCode: noHttpCoverage }`,
|
|
84
|
+
* no network call.
|
|
85
|
+
* - Network/JSON-parse errors are caught and surfaced via the callback,
|
|
86
|
+
* never thrown. Network errors are also reported through the optional
|
|
87
|
+
* `errorReporter` so any registered IErrorReportingService can observe
|
|
88
|
+
* them (matches the pattern used by identifyRequest in identityApiClient).
|
|
89
|
+
*/
|
|
90
|
+
export const sendSearchRequest = async (
|
|
91
|
+
knownIdentities: UserIdentities,
|
|
92
|
+
apiKey: string,
|
|
93
|
+
requestBuilder: () => Omit<IIdentitySearchRequestBody, 'known_identities'>,
|
|
94
|
+
searchUrl: string,
|
|
95
|
+
callback: IdentitySearchCallback,
|
|
96
|
+
logger: SDKLoggerApi,
|
|
97
|
+
uploader?: AsyncUploader,
|
|
98
|
+
errorReporter?: IErrorReportingService,
|
|
99
|
+
): Promise<void> => {
|
|
100
|
+
// Validate the callback up front. If it isn't a function we have nowhere
|
|
101
|
+
// to deliver a result to, so log and bail out without invoking anything.
|
|
102
|
+
if (!isFunction(callback)) {
|
|
103
|
+
logger.error(
|
|
104
|
+
'search called without a callback function; skipping request.',
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const safeInvoke = (result: IIdentitySearchResult): void => {
|
|
110
|
+
try {
|
|
111
|
+
callback(result);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
logger.error(
|
|
114
|
+
'Error invoking search callback: ' +
|
|
115
|
+
((e as Error)?.message || String(e)),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const cleanedKnownIdentities: UserIdentities = Validators.removeFalsyIdentityValues(
|
|
121
|
+
{ userIdentities: knownIdentities ?? {} } as IdentityApiData,
|
|
122
|
+
logger,
|
|
123
|
+
).userIdentities;
|
|
124
|
+
|
|
125
|
+
// No usable identifier -> deliver httpCode: noHttpCoverage so callers
|
|
126
|
+
// waiting on the callback (e.g. to clear a loading state) don't hang.
|
|
127
|
+
if (
|
|
128
|
+
!Object.values(cleanedKnownIdentities ?? {}).some(
|
|
129
|
+
(v) => typeof v === 'string' && v.length > 0,
|
|
130
|
+
)
|
|
131
|
+
) {
|
|
132
|
+
logger.verbose(
|
|
133
|
+
'Identity search called with empty identifiers; skipping request.',
|
|
134
|
+
);
|
|
135
|
+
safeInvoke({ httpCode: HTTPCodes.noHttpCoverage });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// No API key -> same: deliver noHttpCoverage rather than hanging.
|
|
140
|
+
if (!apiKey) {
|
|
141
|
+
logger.verbose(
|
|
142
|
+
'search called without a workspace API key; skipping request.',
|
|
143
|
+
);
|
|
144
|
+
safeInvoke({ httpCode: HTTPCodes.noHttpCoverage });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Wrap request setup AND the network call in the try/catch so any throw
|
|
149
|
+
// — from requestBuilder, JSON.stringify (e.g. circular refs), or
|
|
150
|
+
// uploader construction — flows into the catch below and the consumer's
|
|
151
|
+
// callback fires with noHttpCoverage rather than the async function
|
|
152
|
+
// rejecting and the caller hanging on a never-fired callback.
|
|
153
|
+
try {
|
|
154
|
+
const requestEnvelope = requestBuilder();
|
|
155
|
+
|
|
156
|
+
const requestBody: IIdentitySearchRequestBody = {
|
|
157
|
+
...requestEnvelope,
|
|
158
|
+
known_identities: { ...cleanedKnownIdentities },
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const fetchPayload: IIdentitySearchPayload = {
|
|
162
|
+
method: 'post',
|
|
163
|
+
headers: {
|
|
164
|
+
Accept: 'application/json',
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
'x-mp-key': apiKey,
|
|
167
|
+
},
|
|
168
|
+
body: JSON.stringify(requestBody),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const api: AsyncUploader =
|
|
172
|
+
uploader ||
|
|
173
|
+
(window.fetch
|
|
174
|
+
? new FetchUploader(searchUrl)
|
|
175
|
+
: new XHRUploader(searchUrl));
|
|
176
|
+
|
|
177
|
+
logger.verbose('Sending search request to ' + searchUrl);
|
|
178
|
+
const response: Response = await api.upload(fetchPayload, searchUrl);
|
|
179
|
+
|
|
180
|
+
let body: IIdentitySearchResponseBody | undefined;
|
|
181
|
+
|
|
182
|
+
// FetchUploader returns a real Response with .json(); XHRUploader
|
|
183
|
+
// returns an XHR-shaped object with `responseText`. We tolerate both.
|
|
184
|
+
if (isFunction(response.json)) {
|
|
185
|
+
try {
|
|
186
|
+
body = (await response.json()) as IIdentitySearchResponseBody;
|
|
187
|
+
} catch (e) {
|
|
188
|
+
logger.verbose(
|
|
189
|
+
'search response had no parseable JSON body.',
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
const xhrLike = (response as unknown) as XMLHttpRequest;
|
|
194
|
+
if (xhrLike?.responseText) {
|
|
195
|
+
try {
|
|
196
|
+
body = JSON.parse(xhrLike.responseText) as IIdentitySearchResponseBody;
|
|
197
|
+
} catch (e) {
|
|
198
|
+
logger.verbose(
|
|
199
|
+
'search XHR response was not valid JSON.',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (response.status === HTTP_OK) {
|
|
206
|
+
logger.verbose('search received 200 OK.');
|
|
207
|
+
} else if (response.status === HTTP_NOT_FOUND) {
|
|
208
|
+
// 404 NOT_FOUND_ERROR is an expected steady-state outcome and is
|
|
209
|
+
// intentionally not logged as an error.
|
|
210
|
+
logger.verbose('search received 404 (no match).');
|
|
211
|
+
} else {
|
|
212
|
+
logger.verbose(
|
|
213
|
+
'search received non-success status ' + response.status,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
safeInvoke({ httpCode: response.status, body });
|
|
218
|
+
} catch (e) {
|
|
219
|
+
const message = (e as Error)?.message || String(e);
|
|
220
|
+
const reportMessage = 'Error sending search request: ' + message;
|
|
221
|
+
logger.error(reportMessage);
|
|
222
|
+
// Mirror the identity-route pattern in identityApiClient.ts: log to
|
|
223
|
+
// console AND push a structured report through the dispatcher so any
|
|
224
|
+
// registered IErrorReportingService (e.g. the Rokt kit's) can observe
|
|
225
|
+
// the failure.
|
|
226
|
+
errorReporter?.report({
|
|
227
|
+
message: reportMessage,
|
|
228
|
+
code: ErrorCodes.IDENTITY_REQUEST,
|
|
229
|
+
severity: WSDKErrorSeverity.ERROR,
|
|
230
|
+
});
|
|
231
|
+
safeInvoke({ httpCode: HTTPCodes.noHttpCoverage });
|
|
232
|
+
}
|
|
233
|
+
};
|
package/src/identity-utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Constants, { ONE_DAY_IN_SECONDS, MILLIS_IN_ONE_SEC } from './constants';
|
|
2
|
-
import { Dictionary, parseNumber, isObject, generateHash, isEmpty } from './utils';
|
|
2
|
+
import { Dictionary, Environment, parseNumber, isObject, generateHash, generateUniqueId, isEmpty, isFunction } from './utils';
|
|
3
3
|
import { BaseVault } from './vault';
|
|
4
4
|
import Types from './types';
|
|
5
5
|
import {
|
|
@@ -14,8 +14,15 @@ import {
|
|
|
14
14
|
IMParticleUser,
|
|
15
15
|
} from './identity-user-interfaces';
|
|
16
16
|
import { IStore } from './store';
|
|
17
|
+
import type { IMParticleWebSDKInstance } from './mp-instance';
|
|
18
|
+
import {
|
|
19
|
+
IIdentitySearchRequestBody,
|
|
20
|
+
IdentitySearchCallback,
|
|
21
|
+
sendSearchRequest,
|
|
22
|
+
} from './identity/search';
|
|
17
23
|
|
|
18
24
|
const { Identify, Modify, Login, Logout } = Constants.IdentityMethods;
|
|
25
|
+
const { HTTPCodes, Messages } = Constants;
|
|
19
26
|
export const CACHE_HEADER = 'x-mp-max-age' as const;
|
|
20
27
|
|
|
21
28
|
export type IdentityCache = BaseVault<Dictionary<ICachedIdentityCall>>;
|
|
@@ -321,3 +328,76 @@ export const hasExplicitIdentifier = (store: IStore | undefined | null): boolean
|
|
|
321
328
|
}
|
|
322
329
|
return !!store?.SDKConfig?.deviceId;
|
|
323
330
|
};
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Builds the /v1/identify-style envelope (client_sdk, environment,
|
|
334
|
+
* request_id, request_timestamp_ms) used to correlate IDSync requests
|
|
335
|
+
* across endpoints. `known_identities` is omitted so the caller can
|
|
336
|
+
* fold in the search-specific identifiers alongside the envelope.
|
|
337
|
+
*/
|
|
338
|
+
export const buildIdentitySearchEnvelope = (
|
|
339
|
+
environment: Environment,
|
|
340
|
+
): Omit<IIdentitySearchRequestBody, 'known_identities'> => ({
|
|
341
|
+
client_sdk: {
|
|
342
|
+
platform: Constants.platform,
|
|
343
|
+
sdk_vendor: Constants.sdkVendor,
|
|
344
|
+
sdk_version: Constants.sdkVersion,
|
|
345
|
+
},
|
|
346
|
+
environment,
|
|
347
|
+
request_id: generateUniqueId(),
|
|
348
|
+
request_timestamp_ms: Date.now(),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Wires the SDK instance into `sendSearchRequest`: gates on `canLog`,
|
|
353
|
+
* builds the `/v1/search` URL and request envelope, and dispatches.
|
|
354
|
+
* Lives here so the SDK glue (URL building, opt-out gate, dispatcher
|
|
355
|
+
* plumbing) is type-checked instead of being expressed in plain JS.
|
|
356
|
+
*/
|
|
357
|
+
export const executeSearchRequest = (
|
|
358
|
+
mpInstance: IMParticleWebSDKInstance,
|
|
359
|
+
workspaceApiKey: string,
|
|
360
|
+
knownIdentities: UserIdentities,
|
|
361
|
+
callback: IdentitySearchCallback,
|
|
362
|
+
): void => {
|
|
363
|
+
const { _Helpers, _Store, Logger, _ErrorReportingDispatcher } = mpInstance;
|
|
364
|
+
const { identityUrl, isDevelopmentMode } = _Store.SDKConfig;
|
|
365
|
+
|
|
366
|
+
if (!_Helpers.canLog()) {
|
|
367
|
+
Logger.verbose(Messages.InformationMessages.AbandonLogEvent);
|
|
368
|
+
if (isFunction(callback)) {
|
|
369
|
+
try {
|
|
370
|
+
callback({
|
|
371
|
+
httpCode: HTTPCodes.loggingDisabledOrMissingAPIKey,
|
|
372
|
+
});
|
|
373
|
+
} catch (e) {
|
|
374
|
+
Logger.error(
|
|
375
|
+
'Error invoking search callback: ' +
|
|
376
|
+
((e as Error)?.message || String(e)),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// The Search endpoint is colocated with /v1/identify under
|
|
384
|
+
// identityUrl, so we reuse the same service URL builder. We do
|
|
385
|
+
// NOT append the apiKey to the URL — auth is done via x-mp-key.
|
|
386
|
+
const serviceUrl: string = _Helpers.createServiceUrl(identityUrl);
|
|
387
|
+
const searchUrl: string = serviceUrl + 'search?cb=1';
|
|
388
|
+
|
|
389
|
+
const environment: Environment = isDevelopmentMode
|
|
390
|
+
? 'development'
|
|
391
|
+
: 'production';
|
|
392
|
+
|
|
393
|
+
sendSearchRequest(
|
|
394
|
+
knownIdentities,
|
|
395
|
+
workspaceApiKey,
|
|
396
|
+
() => buildIdentitySearchEnvelope(environment),
|
|
397
|
+
searchUrl,
|
|
398
|
+
callback,
|
|
399
|
+
Logger,
|
|
400
|
+
undefined,
|
|
401
|
+
_ErrorReportingDispatcher,
|
|
402
|
+
);
|
|
403
|
+
};
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
mParticleUserCart,
|
|
13
13
|
IIdentityResponse,
|
|
14
14
|
} from './identity-user-interfaces';
|
|
15
|
+
import { IdentitySearchCallback } from './identity/search';
|
|
15
16
|
const { platform, sdkVendor, sdkVersion, HTTPCodes } = Constants;
|
|
16
17
|
|
|
17
18
|
export type IdentityPreProcessResult = {
|
|
@@ -156,8 +157,53 @@ export interface SDKIdentityApi {
|
|
|
156
157
|
destinationUser: IMParticleUser,
|
|
157
158
|
scope?: AliasRequestScope
|
|
158
159
|
): IAliasRequest;
|
|
160
|
+
/**
|
|
161
|
+
* Sends a request to mParticle's IDSync `/v1/search` endpoint to look up
|
|
162
|
+
* a workspace identity without affecting the current user. The callback
|
|
163
|
+
* receives `httpCode` (always) and an optional `body` containing the
|
|
164
|
+
* parsed JSON response. Consumers should gate behaviour on
|
|
165
|
+
* `httpCode === 200`.
|
|
166
|
+
*
|
|
167
|
+
* `workspaceApiKey` is a workspace-specific API key supplied by the
|
|
168
|
+
* caller (from a kit's settings). It is sent as the `x-mp-key` header.
|
|
169
|
+
* The SDK's own workspace token is intentionally not used.
|
|
170
|
+
*/
|
|
171
|
+
search?(
|
|
172
|
+
workspaceApiKey: string,
|
|
173
|
+
knownIdentities: UserIdentities,
|
|
174
|
+
callback: IdentitySearchCallback
|
|
175
|
+
): void;
|
|
159
176
|
}
|
|
160
177
|
|
|
178
|
+
export type {
|
|
179
|
+
IIdentitySearchResult,
|
|
180
|
+
IIdentitySearchResponseBody,
|
|
181
|
+
IdentitySearchCallback,
|
|
182
|
+
} from './identity/search';
|
|
183
|
+
|
|
184
|
+
export interface IUserIdentities {
|
|
185
|
+
customerid?: string;
|
|
186
|
+
email?: string;
|
|
187
|
+
other?: string;
|
|
188
|
+
other2?: string;
|
|
189
|
+
other3?: string;
|
|
190
|
+
other4?: string;
|
|
191
|
+
other5?: string;
|
|
192
|
+
other6?: string;
|
|
193
|
+
other7?: string;
|
|
194
|
+
other8?: string;
|
|
195
|
+
other9?: string;
|
|
196
|
+
other10?: string;
|
|
197
|
+
mobile_number?: string;
|
|
198
|
+
phone_number_2?: string;
|
|
199
|
+
phone_number_3?: string;
|
|
200
|
+
facebook?: string;
|
|
201
|
+
facebookcustomaudienceid?: string;
|
|
202
|
+
google?: string;
|
|
203
|
+
twitter?: string;
|
|
204
|
+
microsoft?: string;
|
|
205
|
+
yahoo?: string;
|
|
206
|
+
}
|
|
161
207
|
export interface IIdentity {
|
|
162
208
|
audienceManager: AudienceManager;
|
|
163
209
|
idCache: BaseVault<Dictionary<ICachedIdentityCall>>;
|
package/src/identity.js
CHANGED
|
@@ -3,6 +3,7 @@ import Types, { IdentityType } from './types';
|
|
|
3
3
|
import {
|
|
4
4
|
cacheOrClearIdCache,
|
|
5
5
|
createKnownIdentities,
|
|
6
|
+
executeSearchRequest,
|
|
6
7
|
tryCacheIdentity,
|
|
7
8
|
} from './identity-utils';
|
|
8
9
|
import AudienceManager from './audienceManager';
|
|
@@ -730,6 +731,32 @@ export default function Identity(mpInstance) {
|
|
|
730
731
|
}
|
|
731
732
|
},
|
|
732
733
|
|
|
734
|
+
/**
|
|
735
|
+
* Search the IDSync Workspace endpoint for a known identity.
|
|
736
|
+
*
|
|
737
|
+
* POSTs to mParticle's `/v1/search` endpoint and invokes `callback`
|
|
738
|
+
* with `{ httpCode, body? }`.
|
|
739
|
+
*
|
|
740
|
+
* The `workspaceApiKey` is a workspace-specific API key supplied by
|
|
741
|
+
* the caller (passed in from a kit's settings). It is intentionally
|
|
742
|
+
* NOT read from the SDK's own workspace token, so that workspace
|
|
743
|
+
* searches can be authorised independently of the host SDK's
|
|
744
|
+
* workspace.
|
|
745
|
+
*
|
|
746
|
+
* @method search
|
|
747
|
+
* @param {String} workspaceApiKey Workspace API key (sent as x-mp-key).
|
|
748
|
+
* @param {Object} knownIdentities A `UserIdentities` map.
|
|
749
|
+
* @param {Function} callback Invoked with the `IIdentitySearchResult`.
|
|
750
|
+
*/
|
|
751
|
+
search: function(workspaceApiKey, knownIdentities, callback) {
|
|
752
|
+
executeSearchRequest(
|
|
753
|
+
mpInstance,
|
|
754
|
+
workspaceApiKey,
|
|
755
|
+
knownIdentities,
|
|
756
|
+
callback
|
|
757
|
+
);
|
|
758
|
+
},
|
|
759
|
+
|
|
733
760
|
/**
|
|
734
761
|
Create a default AliasRequest for 2 MParticleUsers. This will construct the request
|
|
735
762
|
using the sourceUser's firstSeenTime as the startTime, and its lastSeenTime as the endTime.
|
|
@@ -394,6 +394,13 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) {
|
|
|
394
394
|
modify: function(identityApiData, callback) {
|
|
395
395
|
self.getInstance().Identity.modify(identityApiData, callback);
|
|
396
396
|
},
|
|
397
|
+
search: function(workspaceApiKey, knownIdentities, callback) {
|
|
398
|
+
self.getInstance().Identity.search(
|
|
399
|
+
workspaceApiKey,
|
|
400
|
+
knownIdentities,
|
|
401
|
+
callback
|
|
402
|
+
);
|
|
403
|
+
},
|
|
397
404
|
};
|
|
398
405
|
|
|
399
406
|
this.sessionManager = {
|
package/src/public-types.ts
CHANGED
package/src/roktManager.ts
CHANGED
|
@@ -76,6 +76,7 @@ export interface IRoktKit {
|
|
|
76
76
|
selectPlacements: (options: IRoktSelectPlacementsOptions) => Promise<IRoktSelection>;
|
|
77
77
|
setExtensionData<T>(extensionData: IRoktPartnerExtensionData<T>): void;
|
|
78
78
|
use: <T>(name: string) => Promise<T>;
|
|
79
|
+
onShoppableAdsReady(callback: () => void): void;
|
|
79
80
|
launcherOptions?: Dictionary<any>;
|
|
80
81
|
settings?: IRoktKitSettings;
|
|
81
82
|
integrationName?: string;
|
|
@@ -89,6 +90,8 @@ export interface IRoktOptions {
|
|
|
89
90
|
|
|
90
91
|
export type IRoktLauncherOptions = Dictionary<any>;
|
|
91
92
|
|
|
93
|
+
const ON_SHOPPABLE_ADS_READY_METHOD = 'onShoppableAdsReady'
|
|
94
|
+
|
|
92
95
|
// The purpose of this class is to create a link between the Core mParticle SDK and the
|
|
93
96
|
// Rokt Web SDK via a Web Kit.
|
|
94
97
|
// The Rokt Manager should load before the Web Kit and stubs out many of the
|
|
@@ -116,6 +119,7 @@ export default class RoktManager {
|
|
|
116
119
|
private captureTiming?: (metricsName: string) => void;
|
|
117
120
|
private onReadyCallback: (() => void) | null = null;
|
|
118
121
|
private initialized: boolean = false;
|
|
122
|
+
private isShoppableAdsLoaded: boolean = false;
|
|
119
123
|
|
|
120
124
|
/**
|
|
121
125
|
* Sets a callback to be invoked when RoktManager becomes ready
|
|
@@ -450,6 +454,37 @@ export default class RoktManager {
|
|
|
450
454
|
}
|
|
451
455
|
}
|
|
452
456
|
|
|
457
|
+
public onShoppableAdsReady(callback: () => void): void {
|
|
458
|
+
if (!this.kit || !this.isShoppableAdsLoaded) {
|
|
459
|
+
this.deferredCall<void>('onShoppableAdsReady', callback);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
this.kit.onShoppableAdsReady(callback);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
467
|
+
this.logger.error(`Failed to register onShoppableAdsReady callback: ${errorMessage}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
public flushOnShoppableAdsReadyMessageQueue(kit: IRoktKit): void {
|
|
472
|
+
this.kit = kit;
|
|
473
|
+
|
|
474
|
+
this.messageQueue.forEach((message, key) => {
|
|
475
|
+
if (message.methodName === ON_SHOPPABLE_ADS_READY_METHOD) {
|
|
476
|
+
try {
|
|
477
|
+
kit.onShoppableAdsReady(message.payload as () => void);
|
|
478
|
+
} catch (e) {
|
|
479
|
+
this.logger?.error('RoktManager: Error flushing onShoppableAdsReady: ' + e);
|
|
480
|
+
}
|
|
481
|
+
this.messageQueue.delete(key);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
this.isShoppableAdsLoaded = true;
|
|
486
|
+
}
|
|
487
|
+
|
|
453
488
|
/**
|
|
454
489
|
* Hashes an attribute using SHA-256
|
|
455
490
|
*
|