-
Notifications
You must be signed in to change notification settings - Fork 558
feat(odsp-client): lookupTemporaryBlobUrl functionality #25815
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,9 @@ import { | |
| loadExistingContainer, | ||
| type ILoaderProps, | ||
| } from "@fluidframework/container-loader/internal"; | ||
| import type { IContainerRuntimeInternal } from "@fluidframework/container-runtime-definitions/internal"; | ||
| import type { | ||
| FluidObject, | ||
| IConfigProviderBase, | ||
| IRequest, | ||
| ITelemetryBaseLogger, | ||
|
|
@@ -232,6 +234,37 @@ export class OdspClient { | |
| } | ||
|
|
||
| private async getContainerServices(container: IContainer): Promise<IOdspContainerServices> { | ||
| return new OdspContainerServices(container); | ||
| const runtimeInternal = await this.getRuntimeInternal(container); | ||
| // Get the resolved URL for ODSP-specific URL building | ||
| const resolvedUrl = container.resolvedUrl; | ||
| const odspResolvedUrl = | ||
| resolvedUrl && isOdspResolvedUrl(resolvedUrl) ? resolvedUrl : undefined; | ||
| return new OdspContainerServices(container, odspResolvedUrl, runtimeInternal); | ||
| } | ||
|
|
||
| private async getRuntimeInternal( | ||
| container: IContainer, | ||
| ): Promise<IContainerRuntimeInternal | undefined> { | ||
| const entryPoint = await container.getEntryPoint(); | ||
| if ( | ||
| entryPoint !== undefined && | ||
| typeof (entryPoint as IMaybeFluidObjectWithContainerRuntime).IStaticEntryPoint | ||
| ?.extensionStore === "function" | ||
| ) { | ||
| // If the container has a static entry point with an extension store, use that to get the runtime | ||
| return (entryPoint as IMaybeFluidObjectWithContainerRuntime).IStaticEntryPoint | ||
| .extensionStore; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Unclear if this is the best way to go about accessing the runtime internal from the entry point. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead I think I'd expect to plumb a lookupTemporaryBlobStorageId onto IRootDataObject, in general IRootDataObject is the chokepoint for exposing stuff in the runtime to the outside world. |
||
| * We need it for IContainerRuntimeInternal.lookupTemporaryBlobStorageId, | ||
| * and fluid-static guarantees this exists on the container's entry point, but it is not exposed publicly. | ||
| */ | ||
| interface IMaybeFluidObjectWithContainerRuntime extends FluidObject { | ||
| IStaticEntryPoint: { | ||
| extensionStore: IContainerRuntimeInternal; | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,24 +4,94 @@ | |
| */ | ||
|
|
||
| import type { IContainer } from "@fluidframework/container-definitions/internal"; | ||
| import type { IContainerRuntimeInternal } from "@fluidframework/container-runtime-definitions/internal"; | ||
| import type { IFluidHandle } from "@fluidframework/core-interfaces"; | ||
| import { createServiceAudience } from "@fluidframework/fluid-static/internal"; | ||
| import type { IOdspResolvedUrl } from "@fluidframework/odsp-driver-definitions/internal"; | ||
| import { lookupTemporaryBlobStorageId } from "@fluidframework/runtime-utils/internal"; | ||
|
|
||
| import type { | ||
| IOdspAudience, | ||
| OdspContainerServices as IOdspContainerServices, | ||
| } from "./interfaces.js"; | ||
| import { createOdspAudienceMember } from "./odspAudience.js"; | ||
|
|
||
| /** | ||
| * Helper function to build a blob URL from a storage ID using ODSP-specific logic | ||
| * @param storageId - The storage ID of the blob | ||
| * @param resolvedUrl - The ODSP resolved URL containing endpoint information | ||
| * @returns The blob URL if it can be built, undefined otherwise | ||
| */ | ||
| function buildOdspBlobUrl( | ||
| storageId: string, | ||
| resolvedUrl: IOdspResolvedUrl, | ||
| ): string | undefined { | ||
| try { | ||
| const attachmentGETUrl = resolvedUrl.endpoints.attachmentGETStorageUrl; | ||
| if (!attachmentGETUrl) { | ||
| return undefined; | ||
| } | ||
| return `${attachmentGETUrl}/${encodeURIComponent(storageId)}/content`; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Helper function for ODSPClient to lookup blob URLs | ||
| * @param runtimeInternal - The container runtime internal interface | ||
| * @param handle - The blob handle to lookup the URL for | ||
| * @param resolvedUrl - The ODSP resolved URL containing endpoint information | ||
| * @returns The blob URL if found and the blob is not pending, undefined otherwise | ||
| */ | ||
| function lookupOdspBlobURL( | ||
| runtimeInternal: IContainerRuntimeInternal, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The runtime-utils lookupTemporaryBobStorageId takes an IContainerRuntime, so this should just be an IContainerRuntime. |
||
| handle: IFluidHandle, | ||
| resolvedUrl: IOdspResolvedUrl, | ||
| ): string | undefined { | ||
| try { | ||
| if ( | ||
| runtimeInternal !== undefined && | ||
| typeof (runtimeInternal as { lookupTemporaryBlobStorageId?: unknown }) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't need this guard since odsp-client brings along a modern version of the runtime (via fluid-static) and therefore knows it's going to have a version with this function. That said, would be nice to add a more explicit check and throw a more helpful error if it's missing in runtime-utils rather than a plain cast. |
||
| .lookupTemporaryBlobStorageId === "function" | ||
| ) { | ||
| // Get the storage ID from the runtime | ||
| const storageId = lookupTemporaryBlobStorageId(runtimeInternal, handle); | ||
| if (storageId === undefined) { | ||
| return undefined; | ||
| } | ||
|
|
||
| // Build the URL using ODSP-specific logic | ||
| return buildOdspBlobUrl(storageId, resolvedUrl); | ||
| } | ||
| return undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @internal | ||
| */ | ||
| export class OdspContainerServices implements IOdspContainerServices { | ||
| public readonly audience: IOdspAudience; | ||
|
|
||
| public constructor(container: IContainer) { | ||
| public constructor( | ||
| container: IContainer, | ||
| private readonly odspResolvedUrl?: IOdspResolvedUrl, | ||
| private readonly containerRuntimeInternal?: IContainerRuntimeInternal, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here also prefer to pass around an IContainerRuntime. In general my preference is to defer casting to the internal type until just when it's needed. |
||
| ) { | ||
| this.audience = createServiceAudience({ | ||
| container, | ||
| createServiceMember: createOdspAudienceMember, | ||
| }); | ||
| } | ||
|
|
||
| public lookupTemporaryBlobURL(handle: IFluidHandle): string | undefined { | ||
| if (!this.odspResolvedUrl || this.containerRuntimeInternal === undefined) { | ||
| // Can't build URLs without ODSP resolved URL information | ||
| return undefined; | ||
| } | ||
| return lookupOdspBlobURL(this.containerRuntimeInternal, handle, this.odspResolvedUrl); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you have everything you need to create a partially-applied function
lookupTemporaryBlobURL(handle)and hand that function directly to the OdspContainerServices rather than making OdspContainerServices construct it itself. This will let you avoid passing large objects around.