diff --git a/packages/teams-js/src/internal/communication.ts b/packages/teams-js/src/internal/communication.ts index 3f32f1fa81..1153feb9ee 100644 --- a/packages/teams-js/src/internal/communication.ts +++ b/packages/teams-js/src/internal/communication.ts @@ -4,8 +4,8 @@ import { ApiName, ApiVersionNumber, getApiVersionTag } from '../internal/telemetry'; import { FrameContexts } from '../public/constants'; -import { SdkError } from '../public/interfaces'; -import { latestRuntimeApiVersion } from '../public/runtime'; +import { ErrorCode, SdkError } from '../public/interfaces'; +import { latestRuntimeApiVersion, runtime } from '../public/runtime'; import { version } from '../public/version'; import { GlobalVars } from './globalVars'; import { callHandler } from './handlers'; @@ -421,6 +421,21 @@ function sendMessageToParentHelper( ): MessageRequestWithRequiredProperties { const logger = sendMessageToParentHelperLogger; + // May want to move this lower down into sendRequestToTargetWindowHelper so that it can be used more easily for delayed messages + const isOffline = true; + if ( + actionName !== 'initialize' && + actionName !== 'appInitialization.appLoaded' && + actionName !== 'appInitialization.success' && + isOffline && + !runtime.offlineSupportedFunctions?.includes(actionName) + ) { + // initialize is sort of a special snowflake because it gets called before we have a runtime object, what about things you can call before runtime but aren't sent until we have a response? + throw new Error( + `${ErrorCode.OFFLINE_FUNCTIONALITY_NOT_SUPPORTED}, ${actionName} not in ${JSON.stringify(runtime.offlineSupportedFunctions)}`, + ); + } + const targetWindow = Communication.parentWindow; const request = createMessageRequest(apiVersionTag, actionName, args); diff --git a/packages/teams-js/src/public/index.ts b/packages/teams-js/src/public/index.ts index c496cbef71..3910aea493 100644 --- a/packages/teams-js/src/public/index.ts +++ b/packages/teams-js/src/public/index.ts @@ -25,6 +25,7 @@ export { FileOpenPreference, FrameContext, FrameInfo, + isSupportedOffline, LoadContext, LocaleInfo, M365ContentAction, diff --git a/packages/teams-js/src/public/interfaces.ts b/packages/teams-js/src/public/interfaces.ts index fceeedca9f..2532180aa6 100644 --- a/packages/teams-js/src/public/interfaces.ts +++ b/packages/teams-js/src/public/interfaces.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any*/ +import { app } from './app'; import { ChannelType, DialogDimension, HostClientType, HostName, TeamType, UserTeamRole } from './constants'; import { FrameContexts } from './constants'; +import { geoLocation } from './geoLocation'; +import { runtime } from './runtime'; /** * Represents information about tabs for an app @@ -1008,12 +1011,40 @@ export function isSdkError(err: unknown): err is SdkError { return (err as SdkError)?.errorCode !== undefined; } +// Imagine this was in the offline capability and not here. +// Developers could call this to check ahead of time if a function is supported offline before calling it. +// tree shaking? Could make a version of this in each capability but gets hard to use in the communication layer in an abstract way? Maybe that's fine, +// can just check runtime +/* eslint-disable @typescript-eslint/ban-types*/ +/** Imagine this was documentation */ +export function isSupportedOffline(f: Function): boolean { + switch (f) { + case app.initialize: + case app.notifyAppLoaded: + case app.notifySuccess: + // This could be for functions that we want to say they are supported even if they aren't in the runtime + return true; + case geoLocation.getCurrentLocation: + return runtime.offlineSupportedFunctions?.includes('location.getLocation') ?? false; + case geoLocation.hasPermission: + return runtime.offlineSupportedFunctions?.includes('permissions.has') ?? false; + case geoLocation.requestPermission: + return runtime.offlineSupportedFunctions?.includes('permissions.request') ?? false; + default: + return false; + } +} + /** Error codes used to identify different types of errors that can occur while developing apps. */ export enum ErrorCode { /** * API not supported in the current platform. */ NOT_SUPPORTED_ON_PLATFORM = 100, + /** + * API not supported when no internet connection is available. + */ + OFFLINE_FUNCTIONALITY_NOT_SUPPORTED = 101, /** * Internal error encountered while performing the required operation. */ diff --git a/packages/teams-js/src/public/runtime.ts b/packages/teams-js/src/public/runtime.ts index 44aaf2049e..811ec34c5c 100644 --- a/packages/teams-js/src/public/runtime.ts +++ b/packages/teams-js/src/public/runtime.ts @@ -14,6 +14,7 @@ export interface IBaseRuntime { readonly hostVersionsInfo?: HostVersionsInfo; readonly isLegacyTeams?: boolean; readonly supports?: {}; + offlineSupportedFunctions?: string[]; } /** @@ -297,6 +298,7 @@ interface IRuntimeV4 extends IBaseRuntime { readonly image?: {}; }; readonly webStorage?: {}; + offlineSupportedFunctions?: string[]; // this should be readonly but makes it difficult for prototyping }; } // Constant used to set the runtime configuration @@ -686,9 +688,11 @@ export function applyRuntimeConfig(runtimeConfig: IBaseRuntime): void { }; } applyRuntimeConfigLogger('Fast-forwarding runtime %o', runtimeConfig); - const ffRuntimeConfig = fastForwardRuntime(runtimeConfig); + const ffRuntimeConfig: IRuntimeV4 = fastForwardRuntime(runtimeConfig); + ffRuntimeConfig.offlineSupportedFunctions = ['location.getLocation', 'permissions.has', 'permissions.request']; // Func value, may be able to use telemetry data instead but a little more convoluted applyRuntimeConfigLogger('Applying runtime %o', ffRuntimeConfig); runtime = deepFreeze(ffRuntimeConfig); + console.log(`runtime: ${JSON.stringify(runtime)}`); } export function setUnitializedRuntime(): void {