mirror of
https://github.com/rxliuli/apps.apple.com.git
synced 2025-11-10 01:40:32 +00:00
init commit
This commit is contained in:
1
shared/metrics-8/src/impressions/constants.ts
Normal file
1
shared/metrics-8/src/impressions/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const IMPRESSION_CONTEXT_NAME = 'metrics:impression' as const;
|
||||
252
shared/metrics-8/src/impressions/index.ts
Normal file
252
shared/metrics-8/src/impressions/index.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import type { Logger, LoggerFactory } from '@amp/web-apps-logger/src/types';
|
||||
import { IMPRESSION_CONTEXT_NAME } from './constants';
|
||||
import { createSvelteImpressionAction } from './utils/svelte/impressions-svelte-action';
|
||||
import type {
|
||||
AppImpressionModel,
|
||||
ImpressionSettings,
|
||||
ImpressionsInstance,
|
||||
} from './types';
|
||||
import type {
|
||||
ImpressionObserver,
|
||||
newInstanceWithMetricsConfig,
|
||||
} from '@amp-metrics/mt-impressions-observer';
|
||||
import type { ClickstreamProcessor } from '@amp-metrics/mt-metricskit-processor-clickstream';
|
||||
|
||||
/**
|
||||
* Adapter class to handle interactions with
|
||||
* metricsKit impression observer.
|
||||
*/
|
||||
export class Impressions implements ImpressionsInstance {
|
||||
private readonly logger: Logger;
|
||||
private impressionObserverInstance: ImpressionObserver | undefined;
|
||||
private hasInitialized: boolean = false;
|
||||
private impressionDataMap: Map<HTMLElement, any> = new Map();
|
||||
private currentSnapshot: Record<string, unknown>[] = [];
|
||||
private readonly impressionSettings: ImpressionSettings | undefined;
|
||||
|
||||
constructor(
|
||||
loggerFactory: LoggerFactory,
|
||||
context: Map<string, unknown>,
|
||||
settings?: ImpressionSettings,
|
||||
) {
|
||||
this.logger = loggerFactory.loggerFor(IMPRESSION_CONTEXT_NAME);
|
||||
this.impressionSettings = settings;
|
||||
|
||||
context.set(IMPRESSION_CONTEXT_NAME, this);
|
||||
}
|
||||
|
||||
async init(
|
||||
makeImpressionObserver: typeof newInstanceWithMetricsConfig,
|
||||
clickStreamInstance: ClickstreamProcessor,
|
||||
) {
|
||||
if (this.hasInitialized) {
|
||||
this.logger.warn(
|
||||
'Ignoring, Impressions.init() can only be called once',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const options = { root: document, rootMargin: '0px' };
|
||||
const config = clickStreamInstance.config;
|
||||
const impressionObserver: ImpressionObserver =
|
||||
await makeImpressionObserver(config, options);
|
||||
|
||||
impressionObserver.setDelegate({
|
||||
extractImpressionInfo: (domNode: HTMLElement) => {
|
||||
const dataMap = this.impressionDataMap;
|
||||
const nodeMetricData = dataMap.get(domNode);
|
||||
if (nodeMetricData) {
|
||||
const impressionData = nodeMetricData.impressionMetrics;
|
||||
impressionData.location =
|
||||
clickStreamInstance.utils.eventFields.buildLocationStructure(
|
||||
domNode,
|
||||
(node: HTMLElement) => {
|
||||
const metrics = dataMap.get(node);
|
||||
if (metrics?.location) {
|
||||
return metrics.location;
|
||||
}
|
||||
return;
|
||||
},
|
||||
);
|
||||
return impressionData;
|
||||
} else {
|
||||
this.logger.warn('no impression data found for', domNode);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.impressionObserverInstance = impressionObserver;
|
||||
this.impressionDataMap.forEach((_value, node) => {
|
||||
this.logger.debug('observing deffered node', node);
|
||||
this.impressionObserverInstance?.observe(node);
|
||||
});
|
||||
this.hasInitialized = true;
|
||||
|
||||
this.logger.debug('impressions initialized');
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return this.impressionSettings;
|
||||
}
|
||||
|
||||
isEnabled(event: 'click' | 'exit' | 'impressions'): boolean {
|
||||
if (this.impressionSettings?.captureType === 'jet') {
|
||||
return (
|
||||
this.impressionSettings?.metricsKitEvents?.includes(event) ??
|
||||
false
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
consumeImpressions(): Record<string, unknown>[] | undefined {
|
||||
if (this.hasInitialized) {
|
||||
this.logger.debug('consuming impression metrics');
|
||||
return this.impressionObserverInstance?.consumeImpressions();
|
||||
}
|
||||
this.logger.warn('impressions not avaiable yet');
|
||||
return;
|
||||
}
|
||||
|
||||
captureSnapshotImpression(): Record<string, unknown>[] | undefined {
|
||||
const snapshot =
|
||||
this.impressionObserverInstance?.snapshotImpressions() ?? [];
|
||||
|
||||
// if the current page already transitioned. fallback to the snapshot we captured before transition
|
||||
if (snapshot.length === 0) {
|
||||
return this.getSnapshotImpression();
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
getSnapshotImpression(): Record<string, unknown>[] | undefined {
|
||||
if (this.hasInitialized) {
|
||||
return this.currentSnapshot;
|
||||
}
|
||||
this.logger.warn('impressions not avaiable yet');
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentSnapshot(): void {
|
||||
if (this.hasInitialized) {
|
||||
this.logger.debug('capturing impression snapshot');
|
||||
this.currentSnapshot =
|
||||
this.impressionObserverInstance?.snapshotImpressions() ?? [];
|
||||
} else {
|
||||
this.logger.warn('impressions not avaiable yet');
|
||||
}
|
||||
}
|
||||
|
||||
get nodeList() {
|
||||
const impressionClass = this;
|
||||
|
||||
return new Proxy(impressionClass.impressionDataMap, {
|
||||
get(target, prop, receiver) {
|
||||
const orginalFn = Reflect.get(target, prop, receiver);
|
||||
|
||||
// overriding 'set' to also be able to observe
|
||||
if (prop === 'set') {
|
||||
return (
|
||||
node: HTMLElement,
|
||||
value: Record<string, unknown>,
|
||||
) => {
|
||||
if (impressionClass.hasInitialized) {
|
||||
impressionClass.logger.debug(
|
||||
'observing',
|
||||
node,
|
||||
value,
|
||||
);
|
||||
|
||||
impressionClass.impressionObserverInstance?.observe(
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
return orginalFn.bind(target)(node, value);
|
||||
};
|
||||
}
|
||||
|
||||
// overriding 'delete' to also be able to unobserve
|
||||
if (prop === 'delete') {
|
||||
return (node: HTMLElement) => {
|
||||
if (impressionClass.hasInitialized) {
|
||||
impressionClass.logger.debug('unobserve', node);
|
||||
impressionClass.impressionObserverInstance?.unobserve(
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
return orginalFn.bind(target)(node);
|
||||
};
|
||||
}
|
||||
|
||||
return orginalFn.bind(target);
|
||||
},
|
||||
set(target, prop, value) {
|
||||
return Reflect.set(target, prop, value);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Noop for above
|
||||
*/
|
||||
class ServerNoopImpressions implements ImpressionsInstance {
|
||||
readonly nodeList: WeakMap<any, any>;
|
||||
constructor() {
|
||||
this.nodeList = new WeakMap();
|
||||
}
|
||||
setCurrentSnapshot(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current Impression instance from the Svelte context.
|
||||
*
|
||||
* @return The current instance of Impression
|
||||
*/
|
||||
export function generateBrowserImpressionsContextGetter(
|
||||
getContext: (context: string) => unknown,
|
||||
): () => AppImpressionModel {
|
||||
return function getImpressions(): AppImpressionModel {
|
||||
const impressions = getContext(IMPRESSION_CONTEXT_NAME) as
|
||||
| Impressions
|
||||
| undefined;
|
||||
|
||||
if (!impressions) {
|
||||
const noopImpressions = new ServerNoopImpressions();
|
||||
return {
|
||||
captureImpressions: (_node: any, _impressionsData: any) => {
|
||||
return {
|
||||
destroy() {},
|
||||
};
|
||||
},
|
||||
impressions: noopImpressions,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
captureImpressions: createSvelteImpressionAction(impressions),
|
||||
impressions,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Server No-op for generateImpressionsContextGetter
|
||||
*
|
||||
*/
|
||||
export function generateServerImpressionsContextGetter(
|
||||
_getContext: (context: string) => unknown,
|
||||
): () => AppImpressionModel {
|
||||
const impressions = new ServerNoopImpressions();
|
||||
return () => ({
|
||||
captureImpressions: (_node: any, _impressionsData: any) => {
|
||||
return {
|
||||
destroy() {},
|
||||
};
|
||||
},
|
||||
impressions,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user