Skip to content

Commit 81e33bf

Browse files
committed
Add support for region-based feature flags
1 parent 1de9425 commit 81e33bf

File tree

7 files changed

+66
-4
lines changed

7 files changed

+66
-4
lines changed

localtypings/pxtarget.d.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,11 @@ declare namespace pxt {
327327
skipCloudBuild?: boolean;
328328
}
329329

330+
interface FeatureFlag {
331+
includeRegions?: string[];
332+
excludeRegions?: string[];
333+
}
334+
330335
interface AppTheme {
331336
id?: string;
332337
name?: string;
@@ -503,8 +508,6 @@ declare namespace pxt {
503508
tutorialExplicitHints?: boolean; // allow use explicit hints
504509
errorList?: boolean; // error list experiment
505510
embedBlocksInSnapshot?: boolean; // embed blocks xml in right-click snapshot
506-
blocksErrorList?: boolean; // blocks error list
507-
aiErrorHelp?: boolean; // Enable AI assistance for errors
508511
identity?: boolean; // login with identity providers
509512
assetEditor?: boolean; // enable asset editor view (in blocks/text toggle)
510513
disableMemoryWorkspaceWarning?: boolean; // do not warn the user when switching to in memory workspace
@@ -530,6 +533,7 @@ declare namespace pxt {
530533
timeMachineSnapshotInterval?: number; // An interval in milliseconds at which to take full project snapshots in project history. Defaults to 15 minutes
531534
adjustBlockContrast?: boolean; // If set to true, all block colors will automatically be adjusted to have a contrast ratio of 4.5 with text
532535
pxtJsonOptions?: PxtJsonOption[];
536+
enabledFeatures?: pxt.Map<FeatureFlag>;
533537
}
534538

535539
interface DownloadDialogTheme {

pxtlib/cloud.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace pxt.cloud {
77

88
type BackendUrls = typeof DEV_BACKEND_PROD | typeof DEV_BACKEND_STAGING | typeof DEV_BACKEND_LOCALHOST;
99
export const DEV_BACKEND: BackendUrls = DEV_BACKEND_STAGING;
10+
export const DEV_REGION = "US";
11+
1012

1113
export function devBackendType(): DevBackendType {
1214
if (DEV_BACKEND === DEV_BACKEND_PROD) return "prod";

pxtlib/emitter/cloud.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace pxt.Cloud {
88
export let localToken = "";
99
let _isOnline = true;
1010
export let onOffline = () => { };
11+
let region: string = undefined;
1112

1213
function offlineError(url: string) {
1314
let e: any = new Error(Util.lf("Cannot access {0} while offline", url));
@@ -310,6 +311,38 @@ namespace pxt.Cloud {
310311
return undefined;
311312
}
312313

314+
export async function initRegionAsync(): Promise<void> {
315+
if (BrowserUtils.isLocalHost()) {
316+
region = cloud.DEV_REGION;
317+
return;
318+
}
319+
320+
if (region !== undefined || !pxt.webConfig?.cdnUrl) {
321+
return;
322+
}
323+
324+
const url = new URL("geo", pxt.webConfig.cdnUrl).toString();
325+
const options: Util.HttpRequestOptions = { url };
326+
327+
try {
328+
const response = await Util.requestAsync(options);
329+
if (response.statusCode !== 200) {
330+
pxt.error(`Failed to get region: ${response.statusCode}`);
331+
}
332+
region = response.text.trim();
333+
} catch (e) {
334+
handleNetworkError(options, e);
335+
}
336+
}
337+
338+
export function getRegion(): string {
339+
if (!region) {
340+
pxt.error("Accessing region before it is initialized. Call initRegionAsync first.");
341+
}
342+
343+
return region;
344+
}
345+
313346
//
314347
// Interfaces used by the cloud
315348
//

pxtlib/util.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,27 @@ namespace ts.pxtc.Util {
18931893
}
18941894
}
18951895
}
1896+
1897+
/**
1898+
* Check if the specified feature is enabled for the user (based on pxtarget configuration and region).
1899+
*/
1900+
export function isFeatureEnabled(featureKey: string): boolean {
1901+
const feature = pxt.appTarget.appTheme?.enabledFeatures?.[featureKey];
1902+
if (!feature) return false;
1903+
1904+
let enabled = true;
1905+
const regionNormalised = pxt.Cloud.getRegion() ? pxt.Cloud.getRegion().toUpperCase() : undefined;
1906+
if (feature.includeRegions) {
1907+
enabled = regionNormalised && feature.includeRegions.some(r => r.toUpperCase() == regionNormalised);
1908+
}
1909+
1910+
// Include and exclude shouldn't really be used together, but if they are, exclude takes precedence
1911+
if (enabled && feature.excludeRegions) {
1912+
enabled = regionNormalised && !feature.excludeRegions.some(r => r.toUpperCase() == regionNormalised);
1913+
}
1914+
1915+
return enabled;
1916+
}
18961917
}
18971918

18981919
namespace ts.pxtc.BrowserImpl {

webapp/src/app.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6312,6 +6312,8 @@ document.addEventListener("DOMContentLoaded", async () => {
63126312
baseUrl: baseUrl,
63136313
code: useLang,
63146314
force: force,
6315+
}).then(async () => {
6316+
return pxt.Cloud.initRegionAsync();
63156317
}).then(() => {
63166318
if (pxt.Util.isLocaleEnabled(useLang)) {
63176319
pxt.BrowserUtils.setCookieLang(useLang);

webapp/src/blocks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ export class Editor extends toolboxeditor.ToolboxEditor {
932932

933933
display(): JSX.Element {
934934
let flyoutOnly = this.parent.state.editorState && this.parent.state.editorState.hasCategories === false;
935-
let showErrorList = pxt.appTarget.appTheme.blocksErrorList;
935+
let showErrorList = pxt.Util.isFeatureEnabled("blocksErrorList");
936936
return (
937937
<div className="blocksAndErrorList">
938938
<div className="blocksEditorOuter">

webapp/src/errorList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class ErrorList extends auth.Component<ErrorListProps, ErrorListState> {
104104
const errorCount = errors.length;
105105
const canDebug = startDebugger && !!errors.find(a => a.stackFrames?.length);
106106

107-
const showErrorHelp = !!getErrorHelp && !!showLoginDialog && pxt.appTarget.appTheme.aiErrorHelp;
107+
const showErrorHelp = !!getErrorHelp && !!showLoginDialog && pxt.Util.isFeatureEnabled("aiErrorHelp");
108108

109109
const helpLoader = (
110110
<div className="error-help-loader" onClick={(e) => e.stopPropagation()}>

0 commit comments

Comments
 (0)