Skip to content

Commit 38d2713

Browse files
committed
deploy: 606b7d0
1 parent bb0c2b0 commit 38d2713

File tree

85 files changed

+1464
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+1464
-188
lines changed

appConfig.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ window.AppConfig = {
3030
"extension_store_url": "https://store.core.ai/src/",
3131
"app_notification_url": "assets/notifications/dev/",
3232
"app_update_url": "https://updates.phcode.io/tauri/update-latest-experimental-build.json",
33+
"extensionTakedownURL": "https://updates.phcode.io/extension_takedown.json",
3334
"linting.enabled_by_default": true,
34-
"build_timestamp": "2025-10-04T09:47:29.841Z",
35+
"build_timestamp": "2025-10-05T04:18:08.166Z",
3536
"googleAnalyticsID": "G-P4HJFPDB76",
3637
"googleAnalyticsIDDesktop": "G-VE5BXWJ0HF",
3738
"mixPanelID": "49c4d164b592be2350fc7af06a259bf3",
@@ -43,7 +44,7 @@ window.AppConfig = {
4344
"bugsnagEnv": "development"
4445
},
4546
"name": "Phoenix Code",
46-
"version": "4.1.2-21563",
47+
"version": "4.1.2-21570",
4748
"apiVersion": "4.1.2",
4849
"homepage": "https://core.ai",
4950
"issues": {

assets/default-project/en.zip

0 Bytes
Binary file not shown.

assets/sample-projects/HTML5.zip

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

assets/sample-projects/explore.zip

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

brackets-min.js

Lines changed: 167 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31670,6 +31670,7 @@ define("extensibility/ExtensionManager", function (require, exports, module) {
3167031670
exports.updateExtensions = updateExtensions;
3167131671
exports.getAvailableUpdates = getAvailableUpdates;
3167231672
exports.cleanAvailableUpdates = cleanAvailableUpdates;
31673+
exports.isExtensionTakenDown = ExtensionLoader.isExtensionTakenDown;
3167331674

3167431675
exports.ENABLED = ENABLED;
3167531676
exports.DISABLED = DISABLED;
@@ -32262,6 +32263,11 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3226232263
</div>
3226332264
</td>
3226432265
<td class="ext-desc">
32266+
{{#isDeprecatedExtension}}
32267+
{{#isInstalled}}
32268+
<div class="alert warning">{{Strings.EXTENSION_DEPRECATED_NOT_LOADED}}</div>
32269+
{{/isInstalled}}
32270+
{{/isDeprecatedExtension}}
3226532271
{{#showInstallButton}}
3226632272
<!-- Warnings when trying to install extension where latest/all versions not compatible -->
3226732273
{{#defaultFeature}}
@@ -32363,8 +32369,8 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3236332369
</tr>
3236432370
`,
3236532371
PreferencesManager = require("preferences/PreferencesManager"),
32366-
warnExtensionIDs = JSON.parse(require("text!extensions/default/DefaultExtensions.json"))
32367-
.warnExtensionStoreExtensions.extensionIDs,
32372+
DefaultExtensions = JSON.parse(require("text!extensions/default/DefaultExtensions.json")),
32373+
warnExtensionIDs = new Set(DefaultExtensions.warnExtensionStoreExtensions.extensionIDs),
3236832374
Metrics = require("utils/Metrics");
3236932375

3237032376

@@ -32683,7 +32689,8 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3268332689
context.isCurrentTheme = entry.installInfo &&
3268432690
(entry.installInfo.metadata.name === ThemeManager.getCurrentTheme().name);
3268532691

32686-
context.defaultFeature = warnExtensionIDs.includes(info.metadata.name);
32692+
context.defaultFeature = warnExtensionIDs.has(info.metadata.name);
32693+
context.isDeprecatedExtension = ExtensionManager.isExtensionTakenDown(info.metadata.name);
3268732694

3268832695
context.allowInstall = context.isCompatible && !context.isInstalled;
3268932696

@@ -32739,9 +32746,9 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3273932746
var isDefaultOrInstalled = this.model.source === "default" || this.model.source === "installed";
3274032747
var isDefaultAndTheme = this.model.source === "default" && context.metadata.theme;
3274132748
context.disablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && !context.disabled
32742-
&& !hasPendingAction && !context.metadata.theme;
32749+
&& !hasPendingAction && !context.metadata.theme && !context.isDeprecatedExtension;
3274332750
context.enablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && context.disabled
32744-
&& !hasPendingAction && !context.metadata.theme;
32751+
&& !hasPendingAction && !context.metadata.theme && !context.isDeprecatedExtension;
3274532752

3274632753
// Copy over helper functions that we share with the registry app.
3274732754
["lastVersionDate", "authorInfo"].forEach(function (helper) {
@@ -32895,11 +32902,11 @@ define("extensibility/ExtensionManagerViewModel", function (require, exports, mo
3289532902

3289632903
var _ = require("thirdparty/lodash");
3289732904

32898-
var ExtensionManager = require("extensibility/ExtensionManager"),
32899-
registry_utils = require("extensibility/registry_utils"),
32900-
EventDispatcher = require("utils/EventDispatcher"),
32901-
Strings = require("strings"),
32902-
PreferencesManager = require("preferences/PreferencesManager");
32905+
const ExtensionManager = require("extensibility/ExtensionManager"),
32906+
registry_utils = require("extensibility/registry_utils"),
32907+
EventDispatcher = require("utils/EventDispatcher"),
32908+
Strings = require("strings"),
32909+
PreferencesManager = require("preferences/PreferencesManager");
3290332910

3290432911
/**
3290532912
* @private
@@ -33176,6 +33183,9 @@ define("extensibility/ExtensionManagerViewModel", function (require, exports, mo
3317633183
return entry.registryInfo && entry.registryInfo.metadata.theme;
3317733184

3317833185
})
33186+
.filter(function (entry) {
33187+
return !ExtensionManager.isExtensionTakenDown(entry.registryInfo.metadata.name);
33188+
})
3317933189
.map(function (entry) {
3318033190
return entry.registryInfo.metadata.name;
3318133191
});
@@ -33424,6 +33434,9 @@ define("extensibility/ExtensionManagerViewModel", function (require, exports, mo
3342433434
.filter(function (key) {
3342533435
return self.extensions[key].installInfo &&
3342633436
self.extensions[key].installInfo.locationType === ExtensionManager.LOCATION_DEFAULT;
33437+
})
33438+
.filter(function (key) {
33439+
return !ExtensionManager.isExtensionTakenDown(key);
3342733440
});
3342833441
this._sortFullSet();
3342933442
this._setInitialFilter();
@@ -112920,6 +112933,7 @@ define("nls/root/strings", {
112920112933
"EXTENSION_INCOMPATIBLE_NEWER": "This extension requires a newer version of {APP_NAME}.",
112921112934
"EXTENSION_INCOMPATIBLE_OLDER": "This extension currently only works with older versions of {APP_NAME}.",
112922112935
"EXTENSION_DEFAULT_FEATURE_PRESENT": "You may not need this extension. {APP_NAME} already has this feature.",
112936+
"EXTENSION_DEPRECATED_NOT_LOADED": "Extension not loaded. It is either deprecated or insecure.",
112923112937
"EXTENSION_LATEST_INCOMPATIBLE_NEWER": "Version {0} of this extension requires a newer version of {APP_NAME}. But you can install the earlier version {1}.",
112924112938
"EXTENSION_LATEST_INCOMPATIBLE_OLDER": "Version {0} of this extension only works with older versions of {APP_NAME}. But you can install the earlier version {1}.",
112925112939
"EXTENSION_NO_DESCRIPTION": "No description",
@@ -174797,6 +174811,96 @@ define("utils/ExtensionLoader", function (require, exports, module) {
174797174811
PathUtils = require("thirdparty/path-utils/path-utils"),
174798174812
DefaultExtensions = JSON.parse(require("text!extensions/default/DefaultExtensions.json"));
174799174813

174814+
// takedown/dont load extensions that are compromised at app start - start
174815+
const EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY = "PH_EXTENSION_TAKEDOWN_LIST";
174816+
174817+
function _getTakedownListLS() {
174818+
try{
174819+
let list = localStorage.getItem(EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY);
174820+
if(list) {
174821+
list = JSON.parse(list);
174822+
if (Array.isArray(list)) {
174823+
return list;
174824+
}
174825+
}
174826+
} catch (e) {
174827+
console.error(e);
174828+
}
174829+
return [];
174830+
}
174831+
174832+
const loadedExtensionIDs = new Set();
174833+
let takedownExtensionList = new Set(_getTakedownListLS());
174834+
174835+
const EXTENSION_TAKEDOWN_URL = brackets.config.extensionTakedownURL;
174836+
174837+
function _anyTakenDownExtensionLoaded() {
174838+
if (takedownExtensionList.size === 0 || loadedExtensionIDs.size === 0) {
174839+
return [];
174840+
}
174841+
let smaller;
174842+
let larger;
174843+
174844+
if (takedownExtensionList.size < loadedExtensionIDs.size) {
174845+
smaller = takedownExtensionList;
174846+
larger = loadedExtensionIDs;
174847+
} else {
174848+
smaller = loadedExtensionIDs;
174849+
larger = takedownExtensionList;
174850+
}
174851+
174852+
const matches = [];
174853+
174854+
for (const id of smaller) {
174855+
if (larger.has(id)) {
174856+
matches.push(id);
174857+
}
174858+
}
174859+
174860+
return matches;
174861+
}
174862+
174863+
function fetchWithTimeout(url, ms) {
174864+
const c = new AbortController();
174865+
const t = setTimeout(() => c.abort(), ms);
174866+
return fetch(url, { signal: c.signal }).finally(() => clearTimeout(t));
174867+
}
174868+
174869+
// we dont want a restart after user does too much in the app causing data loss. So we wont reload after 20 seconds.
174870+
fetchWithTimeout(EXTENSION_TAKEDOWN_URL, 20000)
174871+
.then(response => {
174872+
if (!response.ok) {
174873+
throw new Error(`HTTP ${response.status} - ${response.statusText}`);
174874+
}
174875+
return response.json();
174876+
})
174877+
.then(data => {
174878+
console.log('Extension takedown data:', data);
174879+
if (!Array.isArray(data) || !data.every(x => typeof x === "string")) {
174880+
console.error("Takedown list must be an array of strings.");
174881+
return;
174882+
}
174883+
const dataToWrite = JSON.stringify(data);
174884+
localStorage.setItem(EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY, dataToWrite);
174885+
takedownExtensionList = new Set(data);
174886+
const compromisedExtensionsLoaded = _anyTakenDownExtensionLoaded();
174887+
if(!compromisedExtensionsLoaded.length){
174888+
return;
174889+
}
174890+
// if we are here, we have already loaded some compromised extensions. we need to reload app as soon as
174891+
// possible. no await after this. all sync js calls to prevent extension from tampering with this list.
174892+
const writtenData = localStorage.getItem(EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY);
174893+
if(writtenData !== dataToWrite) {
174894+
// the write did not succeded. local storage write can fail if storage full, if so we may cause infinite
174895+
// reloads here if we dont do the check.
174896+
console.error("Failed to write taken down extension to localstorage");
174897+
return;
174898+
}
174899+
location.reload();
174900+
})
174901+
.catch(console.error);
174902+
// takedown/dont load extensions that are compromised at app start - end
174903+
174800174904
const desktopOnlyExtensions = DefaultExtensions.desktopOnly;
174801174905
const DefaultExtensionsList = Phoenix.isNativeApp ?
174802174906
[...DefaultExtensions.defaultExtensionsList, ...desktopOnlyExtensions]:
@@ -175157,6 +175261,16 @@ define("utils/ExtensionLoader", function (require, exports, module) {
175157175261

175158175262
return promise
175159175263
.then(function (metadata) {
175264+
if (isExtensionTakenDown(metadata.name)) {
175265+
logger.leaveTrail("skip load taken down extension: " + metadata.name);
175266+
console.warn("skip load taken down extension: " + metadata.name);
175267+
return new $.Deferred().reject("disabled").promise();
175268+
}
175269+
175270+
if(metadata.name) {
175271+
loadedExtensionIDs.add(metadata.name);
175272+
}
175273+
175160175274
// No special handling for themes... Let the promise propagate into the ExtensionManager
175161175275
if (metadata && metadata.theme) {
175162175276
return;
@@ -175664,6 +175778,15 @@ define("utils/ExtensionLoader", function (require, exports, module) {
175664175778
return promise;
175665175779
}
175666175780

175781+
function isExtensionTakenDown(extensionID) {
175782+
if(!extensionID){
175783+
// extensions without id can happen with local development. these are never distributed in store.
175784+
// so safe to return false here.
175785+
return false;
175786+
}
175787+
return takedownExtensionList.has(extensionID);
175788+
}
175789+
175667175790

175668175791
EventDispatcher.makeEventDispatcher(exports);
175669175792

@@ -175687,6 +175810,7 @@ define("utils/ExtensionLoader", function (require, exports, module) {
175687175810
exports.testExtension = testExtension;
175688175811
exports.loadAllExtensionsInNativeDirectory = loadAllExtensionsInNativeDirectory;
175689175812
exports.loadExtensionFromNativeDirectory = loadExtensionFromNativeDirectory;
175813+
exports.isExtensionTakenDown = isExtensionTakenDown;
175690175814
exports.testAllExtensionsInNativeDirectory = testAllExtensionsInNativeDirectory;
175691175815
exports.testAllDefaultExtensions = testAllDefaultExtensions;
175692175816
exports.EVENT_EXTENSION_LOADED = EVENT_EXTENSION_LOADED;
@@ -178384,6 +178508,37 @@ define("utils/NodeUtils", function (require, exports, module) {
178384178508
return false;
178385178509
}
178386178510

178511+
/**
178512+
* Retrieves the operating system username of the current user.
178513+
* This method is only available in native apps.
178514+
*
178515+
* @throws {Error} Throws an error if called in a browser environment.
178516+
* @return {Promise<string>} A promise that resolves to the OS username of the current user.
178517+
*/
178518+
async function getOSUserName() {
178519+
if (!Phoenix.isNativeApp) {
178520+
throw new Error("getOSUserName not available in browser");
178521+
}
178522+
return utilsConnector.execPeer("getOSUserName");
178523+
}
178524+
178525+
let _systemSettingsDir;
178526+
/**
178527+
* Retrieves the directory path for system settings. This method is applicable to native apps only.
178528+
*
178529+
* @return {Promise<string>} A promise that resolves to the path of the system settings directory.
178530+
* @throws {Error} If the method is called in browser app.
178531+
*/
178532+
async function getSystemSettingsDir() {
178533+
if (!Phoenix.isNativeApp) {
178534+
throw new Error("getSystemSettingsDir is sudo folder is win/linux/mac. not available in browser.");
178535+
}
178536+
if(!_systemSettingsDir){
178537+
_systemSettingsDir = await utilsConnector.execPeer("getSystemSettingsDir");
178538+
}
178539+
return _systemSettingsDir;
178540+
}
178541+
178387178542
if(NodeConnector.isNodeAvailable()) {
178388178543
// todo we need to update the strings if a user extension adds its translations. Since we dont support
178389178544
// node extensions for now, should consider when we support node extensions.
@@ -178426,6 +178581,8 @@ define("utils/NodeUtils", function (require, exports, module) {
178426178581
exports.removeDeviceLicenseSystemWide = removeDeviceLicenseSystemWide;
178427178582
exports.isLicensedDeviceSystemWide = isLicensedDeviceSystemWide;
178428178583
exports.getDeviceID = getDeviceID;
178584+
exports.getOSUserName = getOSUserName;
178585+
exports.getSystemSettingsDir = getSystemSettingsDir;
178429178586

178430178587
/**
178431178588
* checks if Node connector is ready

0 commit comments

Comments
 (0)