Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support loading additional supplemental recordings #10616

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
1 change: 0 additions & 1 deletion packages/protocol/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ let gSessionCallbacks: SessionCallbacks | undefined;

export function setSessionCallbacks(sessionCallbacks: SessionCallbacks) {
if (gSessionCallbacks !== undefined) {
console.error("Session callbacks can only be set once");
return;
}

Expand Down
83 changes: 57 additions & 26 deletions packages/shared/client/ReplayClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {

Check failure on line 1 in packages/shared/client/ReplayClient.ts

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'
Result as EvaluationResult,
ExecutionPoint,
FrameId,
Expand Down Expand Up @@ -78,7 +78,7 @@

// eslint-disable-next-line no-restricted-imports
import { addEventListener, client, initSocket, removeEventListener } from "protocol/socket";
import { assert, compareNumericStrings, defer, waitForTime } from "protocol/utils";
import { assert, compareNumericStrings, defer, Deferred, waitForTime } from "protocol/utils";
import { initProtocolMessagesStore } from "replay-next/components/protocol/ProtocolMessagesStore";
import { insert } from "replay-next/src/utils/array";
import { TOO_MANY_POINTS_TO_FIND } from "shared/constants";
Expand All @@ -90,6 +90,7 @@
ReplayClientEvents,
ReplayClientInterface,
SourceLocationRange,
SupplementalSession,
TimeStampedPointWithPaintHash,
} from "./types";

Expand All @@ -111,6 +112,8 @@

private sessionWaiter = defer<SessionId>();

private supplemental: SupplementalSession[] = [];

private focusWindow: TimeStampedPointRange | null = null;

private nextFindPointsId = 1;
Expand All @@ -131,18 +134,42 @@
// Configures the client to use an already initialized session iD.
// This method should be used for apps that use the protocol package directly.
// Apps that only communicate with the Replay protocol through this client should use the initialize method instead.
async configure(sessionId: string): Promise<void> {
async configure(sessionId: string, supplemental: SupplementalSession[]): Promise<void> {
this._sessionId = sessionId;
this._dispatchEvent("sessionCreated");
this.sessionWaiter.resolve(sessionId);

this.supplemental.push(...supplemental);
await this.syncFocusWindow();
}

waitForSession() {
return this.sessionWaiter.promise;
}

private async forEachSession(callback: (sessionId: string, supplementalIndex: number) => Promise<void>) {
const sessionId = await this.waitForSession();
await callback(sessionId, 0);
for (let i = 0; i < this.supplemental.length; i++) {
await callback(this.supplemental[i].sessionId, i + 1);
}
}

private transformSupplementalId(id: string, supplementalIndex: number) {
return `s${supplementalIndex}-${id}`;
}

private async breakdownSupplementalId(id: string): Promise<{ id: string, sessionId: string }> {
const match = /^s(\d+)-(.*)/.exec(id);
if (!match) {
const sessionId = await this.waitForSession();
return { id, sessionId };
}
const supplementalIndex = +match[1];
const supplementalInfo = this.supplemental[supplementalIndex - 1];
assert(supplementalInfo);
return { id: match[2], sessionId: supplementalInfo.sessionId };
}

get loadedRegions(): LoadedRegions | null {
return this._loadedRegions;
}
Expand Down Expand Up @@ -508,24 +535,26 @@
async findSources(): Promise<Source[]> {
const sources: Source[] = [];

await this.waitForSession();

const sessionId = await this.waitForSession();

const newSourceListener = (source: Source) => {
sources.push(source);
};
const newSourcesListener = ({ sources: sourcesList }: newSources) => {
for (const source of sourcesList) {
await this.forEachSession(async (sessionId, supplementalIndex) => {
const newSourceListener = (source: Source) => {
sources.push(source);
}
};
};
const newSourcesListener = ({ sources: sourcesList }: newSources) => {
for (const source of sourcesList) {
if (supplementalIndex) {
source.sourceId = this.transformSupplementalId(source.sourceId, supplementalIndex);
source.generatedSourceIds = source.generatedSourceIds?.map(id => this.transformSupplementalId(id, supplementalIndex));
}
sources.push(source);
}
};

client.Debugger.addNewSourceListener(newSourceListener);
client.Debugger.addNewSourcesListener(newSourcesListener);
await client.Debugger.findSources({}, sessionId);
client.Debugger.removeNewSourceListener(newSourceListener);
client.Debugger.removeNewSourcesListener(newSourcesListener);
client.Debugger.addNewSourceListener(newSourceListener);
client.Debugger.addNewSourcesListener(newSourcesListener);
await client.Debugger.findSources({}, sessionId);
client.Debugger.removeNewSourceListener(newSourceListener);
client.Debugger.removeNewSourcesListener(newSourcesListener);
});

return sources;
}
Expand Down Expand Up @@ -796,14 +825,15 @@
getSessionId = (): SessionId | null => this._sessionId;

async getSourceHitCounts(
sourceId: SourceId,
transformedSourceId: SourceId,
locations: SameLineSourceLocations[],
focusRange: PointRange | null
) {
const sessionId = await this.waitForSession();
const { id: sourceId, sessionId } = await this.breakdownSupplementalId(transformedSourceId);

await this.waitForRangeToBeInFocusRange(focusRange);
const { hits } = await client.Debugger.getHitCounts(
{ sourceId, locations, maxHits: TOO_MANY_POINTS_TO_FIND, range: focusRange || undefined },
{ sourceId, locations, maxHits: TOO_MANY_POINTS_TO_FIND, range: /*focusRange ||*/ undefined },
sessionId
);
return hits;
Expand All @@ -815,10 +845,11 @@
}

async getBreakpointPositions(
sourceId: SourceId,
transformedSourceId: SourceId,
locationRange: SourceLocationRange | null
): Promise<SameLineSourceLocations[]> {
const sessionId = await this.waitForSession();
const { id: sourceId, sessionId } = await this.breakdownSupplementalId(transformedSourceId);

const begin = locationRange ? locationRange.start : undefined;
const end = locationRange ? locationRange.end : undefined;

Expand Down Expand Up @@ -1063,11 +1094,11 @@
}

async streamSourceContents(
sourceId: SourceId,
transformedSourceId: SourceId,
onSourceContentsInfo: (params: sourceContentsInfo) => void,
onSourceContentsChunk: (params: sourceContentsChunk) => void
): Promise<void> {
const sessionId = await this.waitForSession();
const { id: sourceId, sessionId } = await this.breakdownSupplementalId(transformedSourceId);

let pendingChunk = "";
let pendingThrottlePromise: Promise<void> | null = null;
Expand Down
10 changes: 9 additions & 1 deletion packages/shared/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,18 @@ export interface TimeStampedPointWithPaintHash extends TimeStampedPoint {

export type AnnotationListener = (annotation: Annotation) => void;

export interface SupplementalRecording {
recordingId: string;
}

export interface SupplementalSession extends SupplementalRecording {
sessionId: string;
}

export interface ReplayClientInterface {
get loadedRegions(): LoadedRegions | null;
addEventListener(type: ReplayClientEvents, handler: Function): void;
configure(sessionId: string): Promise<void>;
configure(sessionId: string, supplemental: SupplementalSession[]): Promise<void>;
createPause(executionPoint: ExecutionPoint): Promise<createPauseResult>;
evaluateExpression(
pauseId: PauseId,
Expand Down
23 changes: 21 additions & 2 deletions src/ui/actions/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApolloError } from "@apollo/client";

Check failure on line 1 in src/ui/actions/session.ts

View workflow job for this annotation

GitHub Actions / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'
import { processRecordingProgress, uploadedData } from "@replayio/protocol";
import * as Sentry from "@sentry/react";

Expand Down Expand Up @@ -41,6 +41,7 @@
import { setExpectedError, setUnexpectedError } from "./errors";
import { setToolboxLayout, setViewMode } from "./layout";
import { jumpToInitialPausePoint } from "./timeline";
import { SupplementalRecording } from "shared/client/types";

export { setExpectedError, setUnexpectedError };

Expand Down Expand Up @@ -90,6 +91,14 @@
};
}

function getSupplementalRecordings(recordingId: string): SupplementalRecording[] {
switch (recordingId) {
case "d5513383-5986-4de5-ab9d-2a7e1f367e90":
return [{ recordingId: "c54962d6-9ac6-428a-a6af-2bb2bf6633ca" }];
}
return [];
}

function clearRecordingNotAccessibleError(): UIThunkAction {
return async (dispatch, getState) => {
const state = getState();
Expand Down Expand Up @@ -274,7 +283,7 @@
})
);

const sessionId = await createSession(
const doCreateSession = (recordingId: string) => createSession(
recordingId,
experimentalSettings,
focusWindowFromURL !== null ? focusWindowFromURL : undefined,
Expand Down Expand Up @@ -353,12 +362,22 @@
}
);

const sessionId = await doCreateSession(recordingId);
console.log("MainSessionId", JSON.stringify({ recordingId, sessionId }));

const supplementalRecordings = getSupplementalRecordings(recordingId);
const supplemental = await Promise.all(supplementalRecordings.map(async ({ recordingId }) => {
const sessionId = await doCreateSession(recordingId);
console.log("SupplementalSessionId", JSON.stringify({ recordingId, sessionId }));
return { recordingId, sessionId };
}));

Sentry.configureScope(scope => {
scope.setExtra("sessionId", sessionId);
});

window.sessionId = sessionId;
await replayClient.configure(sessionId);
await replayClient.configure(sessionId, supplemental);
const recordingTarget = await recordingTargetCache.readAsync(replayClient);
dispatch(actions.setRecordingTarget(recordingTarget));

Expand Down
Loading