Skip to content

Commit

Permalink
Allow for passing client display name used when attributing changes t…
Browse files Browse the repository at this point in the history
…o the current client in the collab session (#23422)

Allow for passing client display name used when attributing changes to
the current client in the collab session

## Description

This change introduces support for overriding the client display name
used when attributing changes to the current client in the collab
session. This can be used in scenarios where the client display name
should be different from the default client display name, for instance,
when client is running in app-only context.

This is achieved by adding a new optional property `displayName` to the
`ICollabSessionOptions` interface. This interface is passed inside
`HostStoragePolicy` which is specified when constructing document
service factory. If `displayName` is specified, then it is used when
establishing collab session and passed in the body of /joinSession
request to Push service.

---------

Co-authored-by: Andrei Zenkovitch <andreize@microsoft.com>
  • Loading branch information
AndreiZe and Andrei Zenkovitch authored Jan 8, 2025
1 parent 352834c commit ddce270
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface ICacheEntry extends IEntry {

// @alpha (undocumented)
export interface ICollabSessionOptions {
displayName?: string;
// @deprecated
forceAccessTokenViaAuthorizationHeader?: boolean;
// @deprecated
Expand Down
7 changes: 7 additions & 0 deletions packages/drivers/odsp-driver-definitions/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ export interface ICollabSessionOptions {
* @deprecated Due to security reasons we will be passing the token via Authorization header only.
*/
forceAccessTokenViaAuthorizationHeader?: boolean;
/**
* Value indicating the client display name for current session.
* This name will be used in attribution associated with edits made during session.
* This is optional and used only when collab session is being joined by client acting in app-only mode (i.e. without user context).
* If not specified client display name is extracted from the access token that is used to join session.
*/
displayName?: string;
}

/**
Expand Down
13 changes: 11 additions & 2 deletions packages/drivers/odsp-driver/src/odspDelayLoadedDeltaStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class OdspDelayLoadedDeltaStream {
| undefined,
private readonly mc: MonitoringContext,
private readonly cache: IOdspCache,
_hostPolicy: HostStoragePolicy,
private readonly hostPolicy: HostStoragePolicy,
private readonly epochTracker: EpochTracker,
private readonly opsReceived: (ops: ISequencedDocumentMessage[]) => void,
private readonly metadataUpdateHandler: (metadata: Record<string, string>) => void,
Expand Down Expand Up @@ -156,6 +156,8 @@ export class OdspDelayLoadedDeltaStream {
requestWebsocketTokenFromJoinSession,
options,
false /* isRefreshingJoinSession */,
undefined /* clientId */,
this.hostPolicy.sessionOptions?.displayName,
);
const [websocketEndpoint, websocketToken] = await Promise.all([
joinSessionPromise.catch(annotateAndRethrowConnectionError("joinSession")),
Expand Down Expand Up @@ -273,6 +275,7 @@ export class OdspDelayLoadedDeltaStream {
delta: number,
requestSocketToken: boolean,
clientId: string | undefined,
displayName: string | undefined,
): Promise<void> {
if (this.joinSessionRefreshTimer !== undefined) {
this.clearJoinSessionTimer();
Expand Down Expand Up @@ -301,6 +304,7 @@ export class OdspDelayLoadedDeltaStream {
options,
true /* isRefreshingJoinSession */,
clientId,
displayName,
);
resolve();
}).catch((error) => {
Expand All @@ -314,7 +318,8 @@ export class OdspDelayLoadedDeltaStream {
requestSocketToken: boolean,
options: TokenFetchOptionsEx,
isRefreshingJoinSession: boolean,
clientId?: string,
clientId: string | undefined,
displayName: string | undefined,
): Promise<ISocketStorageDiscovery> {
// If this call is to refresh the join session for the current connection but we are already disconnected in
// the meantime or disconnected and then reconnected then do not make the call. However, we should not have
Expand Down Expand Up @@ -342,6 +347,7 @@ export class OdspDelayLoadedDeltaStream {
requestSocketToken,
options,
isRefreshingJoinSession,
displayName,
).catch((error) => {
if (hasFacetCodes(error) && error.facetCodes !== undefined) {
for (const code of error.facetCodes) {
Expand Down Expand Up @@ -378,6 +384,7 @@ export class OdspDelayLoadedDeltaStream {
requestSocketToken: boolean,
options: TokenFetchOptionsEx,
isRefreshingJoinSession: boolean,
displayName: string | undefined,
): Promise<ISocketStorageDiscovery> {
const disableJoinSessionRefresh = this.mc.config.getBoolean(
"Fluid.Driver.Odsp.disableJoinSessionRefresh",
Expand All @@ -397,6 +404,7 @@ export class OdspDelayLoadedDeltaStream {
options,
disableJoinSessionRefresh,
isRefreshingJoinSession,
displayName,
);
// Emit event only in case it is fetched from the network.
if (joinSessionResponse.sensitivityLabelsInfo !== undefined) {
Expand Down Expand Up @@ -450,6 +458,7 @@ export class OdspDelayLoadedDeltaStream {
response.refreshAfterDeltaMs,
requestSocketToken,
this.currentConnection?.clientId,
displayName,
).catch((error) => {
// Log the error and do nothing as the reconnection would fetch the join session.
this.mc.logger.sendTelemetryEvent(
Expand Down
22 changes: 14 additions & 8 deletions packages/drivers/odsp-driver/src/vroom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { TokenFetchOptionsEx } from "./odspUtils.js";
import { runWithRetry } from "./retryUtils.js";

interface IJoinSessionBody {
requestSocketToken: boolean;
guestDisplayName?: string;
requestSocketToken?: boolean;
displayName?: string;
}

/**
Expand All @@ -38,8 +38,9 @@ interface IJoinSessionBody {
* @param options - Options to fetch the token.
* @param disableJoinSessionRefresh - Whether the caller wants to disable refreshing join session periodically.
* @param isRefreshingJoinSession - whether call is to refresh the session before expiry.
* @param guestDisplayName - display name used to identify guest user joining a session.
* This is optional and used only when collab session is being joined via invite.
* @param displayName - display name used to identify client joining a session.
* This is optional and used only when collab session is being joined by client acting in app-only mode (i.e. without user context).
* If not specified client display name is extracted from the access token that is used to join session.
*/
export async function fetchJoinSession(
urlParts: IOdspUrlParts,
Expand All @@ -52,6 +53,7 @@ export async function fetchJoinSession(
options: TokenFetchOptionsEx,
disableJoinSessionRefresh: boolean | undefined,
isRefreshingJoinSession: boolean,
displayName: string | undefined,
): Promise<ISocketStorageDiscovery> {
const apiRoot = getApiRoot(new URL(urlParts.siteUrl));
const url = `${apiRoot}/drives/${urlParts.driveId}/items/${urlParts.itemId}/${path}?ump=1`;
Expand Down Expand Up @@ -89,11 +91,15 @@ export async function fetchJoinSession(
}
postBody += `_post: 1\r\n`;

let requestBody: IJoinSessionBody | undefined;
if (requestSocketToken) {
const body: IJoinSessionBody = {
requestSocketToken: true,
};
postBody += `\r\n${JSON.stringify(body)}\r\n`;
requestBody = { ...requestBody, requestSocketToken: true };
}
if (displayName) {
requestBody = { ...requestBody, displayName };
}
if (requestBody) {
postBody += `\r\n${JSON.stringify(requestBody)}\r\n`;
}
postBody += `\r\n--${formBoundary}--`;
const headers: { [index: string]: string } = {
Expand Down
1 change: 1 addition & 0 deletions packages/test/test-drivers/src/odspDriverApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const odspOpsCaching: OptionsMatrix<IOpsCachingPolicy> = {
const odspSessionOptions: OptionsMatrix<ICollabSessionOptions> = {
unauthenticatedUserDisplayName: [undefined],
forceAccessTokenViaAuthorizationHeader: [undefined],
displayName: [undefined],
};

/**
Expand Down

0 comments on commit ddce270

Please sign in to comment.