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

offline mode draft 1 #2474

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/teams-js/src/internal/communication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions packages/teams-js/src/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export {
FileOpenPreference,
FrameContext,
FrameInfo,
isSupportedOffline,
LoadContext,
LocaleInfo,
M365ContentAction,
Expand Down
31 changes: 31 additions & 0 deletions packages/teams-js/src/public/interfaces.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down
6 changes: 5 additions & 1 deletion packages/teams-js/src/public/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface IBaseRuntime {
readonly hostVersionsInfo?: HostVersionsInfo;
readonly isLegacyTeams?: boolean;
readonly supports?: {};
offlineSupportedFunctions?: string[];
}

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Loading