Skip to content

Reroute cloud through IPC channel #10285

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

Draft
wants to merge 16 commits into
base: master
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
1 change: 1 addition & 0 deletions localtypings/pxtarget.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ declare namespace pxt {
timeMachineDiffInterval?: number; // An interval in milliseconds at which to take diffs to store in project history. Defaults to 5 minutes
timeMachineSnapshotInterval?: number; // An interval in milliseconds at which to take full project snapshots in project history. Defaults to 15 minutes
adjustBlockContrast?: boolean; // If set to true, all block colors will automatically be adjusted to have a contrast ratio of 4.5 with text
ipcIdentityProxy?: boolean; // for use with the in game minecraft experience only. If true, proxies all identity API requests through the ipc channel
pxtJsonOptions?: PxtJsonOption[];
}

Expand Down
87 changes: 87 additions & 0 deletions localtypings/pxteditor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ declare namespace pxt.editor {
| "serviceworkerregistered"
| "runeval"
| "precachetutorial"
| "cloudproxy"
| "setcolorthemebyid"

// package extension messasges
Expand Down Expand Up @@ -1412,6 +1413,92 @@ declare namespace pxt.editor {

type AssetEditorEvent = AssetEditorRequestSaveEvent | AssetEditorReadyEvent;

type CloudProject = {
id: string;
shareId?: string;
header: string;
text: string;
version: string;

// minecraft specific
driveItemId?: string;
};

interface BaseCloudProxyRequest extends EditorMessageRequest {
action: "cloudproxy";
operation: string;
response: true;
}

interface CloudProxyUserRequest extends BaseCloudProxyRequest {
operation: "user";
}

interface CloudProxyListRequest extends BaseCloudProxyRequest {
operation: "list";
headerIds?: string[];
}

interface CloudProxyGetRequest extends BaseCloudProxyRequest {
operation: "get";
headerId: string;
}

interface CloudProxySetRequest extends BaseCloudProxyRequest {
operation: "set";
project: CloudProject;
}

interface CloudProxyDeleteRequest extends BaseCloudProxyRequest {
operation: "delete";
headerId: string;
}

type CloudProxyRequest =
| CloudProxyUserRequest
| CloudProxyListRequest
| CloudProxyGetRequest
| CloudProxySetRequest
| CloudProxyDeleteRequest;


interface BaseCloudProxyResponse extends EditorMessageResponse {
action: "cloudproxy";
operation: string;
resp: pxt.auth.ApiResult<any>;
}

interface CloudProxyUserResponse extends BaseCloudProxyResponse {
operation: "user";
}

interface CloudProxyListResponse extends BaseCloudProxyResponse {
operation: "list";
resp: pxt.auth.ApiResult<CloudProject[]>;
}

interface CloudProxyGetResponse extends BaseCloudProxyResponse {
operation: "get";
resp: pxt.auth.ApiResult<CloudProject>;
}

interface CloudProxySetResponse extends BaseCloudProxyResponse {
operation: "set";
resp: pxt.auth.ApiResult<string>;
}

interface CloudProxyDeleteResponse extends BaseCloudProxyResponse {
operation: "delete";
resp: pxt.auth.ApiResult<string>;
}

type CloudProxyResponse =
| CloudProxyUserResponse
| CloudProxyListResponse
| CloudProxyGetResponse
| CloudProxySetResponse
| CloudProxyDeleteResponse;

export interface TextEdit {
range: monaco.Range;
replacement: string;
Expand Down
12 changes: 12 additions & 0 deletions pxtlib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ namespace pxt.auth {
// Last known auth token state. This is provided as a convenience for legacy methods that cannot be made async.
// Preference hasAuthTokenAsync() over taking a dependency on this cached value.
export let cachedHasAuthToken = false;
export let cachedAuthOffline = false;

async function setLocalStorageValueAsync(key: string, value: string | undefined): Promise<void> {
if (!!value)
Expand All @@ -127,6 +128,10 @@ namespace pxt.auth {
return await setLocalStorageValueAsync(CSRF_TOKEN_KEY, token);
}
export async function hasAuthTokenAsync(): Promise<boolean> {
if (proxyIdentityThroughIPC()) {
cachedHasAuthToken = true;
return true;
}
return !!(await getAuthTokenAsync());
}
async function delAuthTokenAsync(): Promise<void> {
Expand Down Expand Up @@ -759,9 +764,16 @@ namespace pxt.auth {
}

export function hasIdentity(): boolean {
if (proxyIdentityThroughIPC()) {
return true;
}
return !authDisabled && !pxt.BrowserUtils.isPxtElectron() && identityProviders().length > 0;
}

export function proxyIdentityThroughIPC(): boolean {
return pxt.appTarget.appTheme.ipcIdentityProxy;
}

function idpEnabled(idp: pxt.IdentityProviderId): boolean {
return identityProviders().filter(prov => prov.id === idp).length > 0;
}
Expand Down
3 changes: 2 additions & 1 deletion react-common/components/share/ShareInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const ShareInfo = (props: ShareInfoProps) => {
const prePublish = shareState === "share" || shareState === "publishing";
const isPublished = shareState === "publish" || shareState === "publish-vscode";
const showDescription = !isPublished;
const showPersistentShareCheckbox = isLoggedIn && hasProjectBeenPersistentShared && !pxt.auth.proxyIdentityThroughIPC();
let qrCodeButtonRef: HTMLButtonElement;
let inputRef: HTMLInputElement;
let kioskInputRef: HTMLInputElement;
Expand Down Expand Up @@ -355,7 +356,7 @@ export const ShareInfo = (props: ShareInfoProps) => {
onBlur={setName}
onEnterKey={setName}
preserveValueOnBlur={true} />
{isLoggedIn && hasProjectBeenPersistentShared && <Checkbox
{showPersistentShareCheckbox && <Checkbox
id="persistent-share-checkbox"
label={lf("Update existing share link for this project")}
isChecked={!isAnonymous}
Expand Down
288 changes: 288 additions & 0 deletions webapp/public/cloudframe.html

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion webapp/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import { NavigateRegionsOverlay } from "./components/NavigateRegionsOverlay";
import { parseTourStepsAsync } from "./onboarding";
import { initGitHubDb } from "./idbworkspace";
import { BlockDefinition, CategoryNameID } from "./toolbox";
import { MinecraftAuthClient } from "./minecraftAuthClient";
import { FeedbackModal } from "../../react-common/components/controls/Feedback/Feedback";
import { ThemeManager } from "../../react-common/components/theming/themeManager";
import { applyPolyfills } from "./polyfills";
Expand Down Expand Up @@ -4375,7 +4376,7 @@ export class ProjectView
const hasIdentity = auth.hasIdentity() && this.isLoggedIn();

try {
const persistentPublish = hasIdentity && !forceAnonymous;
const persistentPublish = hasIdentity && !forceAnonymous && !pxt.auth.proxyIdentityThroughIPC();
const id = await this.publishCurrentHeaderAsync(persistentPublish, screenshotUri);
return await this.getShareUrl(id, persistentPublish);
} catch (e) {
Expand Down Expand Up @@ -6128,6 +6129,10 @@ document.addEventListener("DOMContentLoaded", async () => {

initGitHubDb();

if (pxt.auth.proxyIdentityThroughIPC()) {
auth.overrideAuthClient(() => new MinecraftAuthClient());
}

// DO NOT put any async code before this line! The serviceworker must be initialized before
// the window load event fires
appcache.init(() => theEditor.reloadEditor());
Expand Down
21 changes: 18 additions & 3 deletions webapp/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import * as core from "./core";
import * as data from "./data";
import * as cloud from "./cloud";
import * as workspace from "./workspace";
import { MinecraftAuthClient } from "./minecraftAuthClient";

/**
* Virtual API keys
*/
const MODULE = "auth";
const FIELD_USER_PROFILE = "profile";
const FIELD_LOGGED_IN = "logged-in";
const FIELD_OFFLINE = "offline";
export const USER_PROFILE = `${MODULE}:${FIELD_USER_PROFILE}`;
export const LOGGED_IN = `${MODULE}:${FIELD_LOGGED_IN}`;
export const OFFLINE = `${MODULE}:${FIELD_OFFLINE}`;

const USER_PREF_MODULE = "user-pref";
const FIELD_USER_PREFERENCES = "preferences";
Expand Down Expand Up @@ -39,10 +42,12 @@ export class Component<TProps, TState> extends data.Component<TProps, TState> {
}
}

class AuthClient extends pxt.auth.AuthClient {
export class AuthClient extends pxt.auth.AuthClient {
protected async onSignedIn(): Promise<void> {
const state = await pxt.auth.getUserStateAsync();
core.infoNotification(lf("Signed in: {0}", pxt.auth.userName(state.profile)));
if (!pxt.auth.proxyIdentityThroughIPC()) {
core.infoNotification(lf("Signed in: {0}", pxt.auth.userName(state.profile)));
}
if (!!workspace.getWorkspaceType())
await cloud.syncAsync();
pxt.storage.setLocal(HAS_USED_CLOUD, "true");
Expand Down Expand Up @@ -101,6 +106,7 @@ class AuthClient extends pxt.auth.AuthClient {
switch (field) {
case FIELD_USER_PROFILE: return hasToken ? { ...state?.profile } : null;
case FIELD_LOGGED_IN: return hasToken && state?.profile != null;
case FIELD_OFFLINE: return pxt.auth.cachedAuthOffline;
}
return null;
}
Expand Down Expand Up @@ -161,12 +167,13 @@ function initVirtualApi() {
}

let authClientPromise: Promise<AuthClient>;
let authClientFactory = () => new AuthClient();

async function clientAsync(): Promise<AuthClient | undefined> {
if (!pxt.auth.hasIdentity()) { return undefined; }
if (authClientPromise) return authClientPromise;
authClientPromise = new Promise<AuthClient>(async (resolve, reject) => {
const cli = new AuthClient();
const cli = authClientFactory();
await cli.initAsync();
await cli.authCheckAsync();
await cli.initialUserPreferencesAsync();
Expand All @@ -175,6 +182,10 @@ async function clientAsync(): Promise<AuthClient | undefined> {
return authClientPromise;
}

export function overrideAuthClient(factory: () => AuthClient) {
authClientFactory = factory;
}

export function hasIdentity(): boolean {
return pxt.auth.hasIdentity();
}
Expand All @@ -183,6 +194,10 @@ export function loggedIn(): boolean {
return data.getData<boolean>(LOGGED_IN);
}

export function isOffline(): boolean {
return data.getData<boolean>(OFFLINE);
}

export function userProfile(): pxt.auth.UserProfile {
return data.getData<pxt.auth.UserProfile>(USER_PROFILE);
}
Expand Down
15 changes: 12 additions & 3 deletions webapp/src/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,17 @@ async function listAsync(hdrs?: Header[]): Promise<Header[]> {
header.cloudVersion = proj.version;
return header;
});

if (!hdrs) {

}
pxt.tickEvent(`identity.cloudApi.list.success`, { count: headers.length });
resolve(headers);
} else {
}
else if (result.statusCode === 404) {
resolve([]);
}
else {
pxt.tickEvent(`identity.cloudApi.list.failed`);
reject(result.err);
}
Expand Down Expand Up @@ -168,7 +176,7 @@ export class CloudTempMetadata {
const h = workspace.getHeader(this.headerId);
if (!h || !h.cloudUserId)
return undefined;
if (!auth.loggedIn())
if (!auth.loggedIn() || auth.isOffline())
return pxt.cloud.cloudStatus["offline"];
if (this._syncStartTime > 0)
return pxt.cloud.cloudStatus["syncing"];
Expand Down Expand Up @@ -421,8 +429,9 @@ async function syncAsyncInternal(opts: SyncAsyncOptions): Promise<pxt.workspace.

async function syncOneUp(local: Header): Promise<void> {
const projShorthand = shortName(local);
let cloudCurrent = local.cloudCurrent && (!fullSync || remoteHeaders.some(h => h.id === local.id))
try {
if (!local.cloudCurrent) {
if (!cloudCurrent) {
if (local.isDeleted) {
// Deleted local project, push to cloud
const res = await toCloud(local, null);
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/headerbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class HeaderBar extends data.Component<ISettingsProps, {}> {
const { home, header, tutorialOptions } = this.props.parent.state;
const isController = pxt.shell.isControllerMode();
const isNativeHost = cmds.isNativeHost();
const hasIdentity = auth.hasIdentity();
const hasIdentity = auth.hasIdentity() && !pxt.auth.proxyIdentityThroughIPC();
const activeEditor = this.props.parent.isPythonActive() ? "Python"
: (this.props.parent.isJavaScriptActive() ? "JavaScript" : "Blocks");

Expand Down
Loading