Skip to content

Commit 13edb60

Browse files
committed
deploy: 606b7d0
1 parent a1a1f15 commit 13edb60

Some content is hidden

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

61 files changed

+2756
-76
lines changed

appConfig.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ window.AppConfig = {
3333
"app_update_url": "https://updates.phcode.io/tauri/update-latest-experimental-build.json",
3434
"extensionTakedownURL": "https://updates.phcode.io/extension_takedown.json",
3535
"linting.enabled_by_default": true,
36-
"build_timestamp": "2025-10-06T07:42:00.906Z",
36+
"build_timestamp": "2025-10-06T14:54:07.286Z",
3737
"googleAnalyticsID": "G-P4HJFPDB76",
3838
"googleAnalyticsIDDesktop": "G-VE5BXWJ0HF",
3939
"mixPanelID": "49c4d164b592be2350fc7af06a259bf3",
@@ -45,7 +45,7 @@ window.AppConfig = {
4545
"bugsnagEnv": "development"
4646
},
4747
"name": "Phoenix Code",
48-
"version": "4.1.2-21581",
48+
"version": "4.1.2-21583",
4949
"apiVersion": "4.1.2",
5050
"homepage": "https://core.ai",
5151
"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: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168427,6 +168427,7 @@ define("services/EntitlementsManager", function (require, exports, module) {
168427168427

168428168428
const EventDispatcher = require("utils/EventDispatcher"),
168429168429
AIControl = require("./ai-control"),
168430+
UserNotifications = require("./UserNotifications"),
168430168431
Strings = require("strings"),
168431168432
StringUtils = require("utils/StringUtils");
168432168433

@@ -168528,6 +168529,36 @@ define("services/EntitlementsManager", function (require, exports, module) {
168528168529
return await LoginService.getEntitlements();
168529168530
}
168530168531

168532+
/**
168533+
* Get notifications array from entitlements. Uses cached entitlements when available.
168534+
* Notifications are used to display in-app promotional messages, alerts, or announcements to users.
168535+
*
168536+
* @returns {Promise<Array>} Array of notification objects, empty array if no notifications available
168537+
*
168538+
* @description Each notification object has the following structure:
168539+
* ```javascript
168540+
* {
168541+
* notificationID: string, // Unique UUID to track if notification was shown
168542+
* title: string, // Notification title
168543+
* htmlContent: string, // HTML content of the notification message
168544+
* validTill: number, // Timestamp when notification expires
168545+
* options: {
168546+
* autoCloseTimeS: number, // Optional: Time in seconds for auto-close. Default: never
168547+
* dismissOnClick: boolean, // Optional: Close on click. Default: true
168548+
* toastStyle: string // Optional: Style class (NOTIFICATION_STYLES_CSS_CLASS.INFO, etc.)
168549+
* }
168550+
* }
168551+
* ```
168552+
*/
168553+
async function getNotifications() {
168554+
const entitlements = await _getEffectiveEntitlements();
168555+
168556+
if (entitlements && entitlements.notifications) {
168557+
return entitlements.notifications;
168558+
}
168559+
return [];
168560+
}
168561+
168531168562
/**
168532168563
* Get live edit is enabled for user, based on his logged in pro-user/trial status.
168533168564
*
@@ -168692,6 +168723,7 @@ define("services/EntitlementsManager", function (require, exports, module) {
168692168723
EntitlementsManager.trigger(EVENT_ENTITLEMENTS_CHANGED);
168693168724
});
168694168725
AIControl.init();
168726+
UserNotifications.init();
168695168727
}
168696168728

168697168729
// Test-only exports for integration testing
@@ -168703,6 +168735,7 @@ define("services/EntitlementsManager", function (require, exports, module) {
168703168735
isInProTrial,
168704168736
getTrialRemainingDays,
168705168737
getRawEntitlements,
168738+
getNotifications,
168706168739
getLiveEditEntitlement,
168707168740
loginToAccount
168708168741
};
@@ -168718,11 +168751,313 @@ define("services/EntitlementsManager", function (require, exports, module) {
168718168751
EntitlementsManager.isInProTrial = isInProTrial;
168719168752
EntitlementsManager.getTrialRemainingDays = getTrialRemainingDays;
168720168753
EntitlementsManager.getRawEntitlements = getRawEntitlements;
168754+
EntitlementsManager.getNotifications = getNotifications;
168721168755
EntitlementsManager.getLiveEditEntitlement = getLiveEditEntitlement;
168722168756
EntitlementsManager.getAIEntitlement = getAIEntitlement;
168723168757
EntitlementsManager.EVENT_ENTITLEMENTS_CHANGED = EVENT_ENTITLEMENTS_CHANGED;
168724168758
});
168725168759

168760+
/*
168761+
* GNU AGPL-3.0 License
168762+
*
168763+
* Copyright (c) 2021 - present core.ai . All rights reserved.
168764+
*
168765+
* This program is free software: you can redistribute it and/or modify it under
168766+
* the terms of the GNU Affero General Public License as published by the Free
168767+
* Software Foundation, either version 3 of the License, or (at your option) any later version.
168768+
*
168769+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
168770+
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
168771+
* See the GNU Affero General Public License for more details.
168772+
*
168773+
* You should have received a copy of the GNU Affero General Public License along
168774+
* with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
168775+
*
168776+
*/
168777+
168778+
/**
168779+
* User Notifications Service
168780+
*
168781+
* This module handles server-sent notifications from the entitlements API.
168782+
* Notifications are displayed as toast messages and acknowledged back to the server.
168783+
*/
168784+
168785+
define("services/UserNotifications", function (require, exports, module) {
168786+
const KernalModeTrust = window.KernalModeTrust;
168787+
if(!KernalModeTrust){
168788+
throw new Error("UserNotifications should have access to KernalModeTrust. Cannot boot without trust ring");
168789+
}
168790+
168791+
const PreferencesManager = require("preferences/PreferencesManager"),
168792+
NotificationUI = require("widgets/NotificationUI");
168793+
168794+
const PREF_NOTIFICATIONS_SHOWN_LIST = "notificationsShownList";
168795+
PreferencesManager.stateManager.definePreference(PREF_NOTIFICATIONS_SHOWN_LIST, "object", {});
168796+
168797+
let EntitlementsManager;
168798+
let LoginService;
168799+
168800+
// In-memory tracking to prevent duplicate notifications during rapid EVENT_ENTITLEMENTS_CHANGED events
168801+
const currentlyShownNotifications = new Set();
168802+
168803+
// Save a copy of window.fetch so that extensions won't tamper with it
168804+
let fetchFn = window.fetch;
168805+
168806+
/**
168807+
* Get the list of notification IDs that have been shown and acknowledged
168808+
* @returns {Object} Map of notificationID -> timestamp
168809+
*/
168810+
function getShownNotifications() {
168811+
return PreferencesManager.stateManager.get(PREF_NOTIFICATIONS_SHOWN_LIST) || {};
168812+
}
168813+
168814+
/**
168815+
* Mark a notification as shown and acknowledged
168816+
* @param {string} notificationID - The notification ID to mark as shown
168817+
*/
168818+
function markNotificationAsShown(notificationID) {
168819+
const shownNotifications = getShownNotifications();
168820+
shownNotifications[notificationID] = Date.now();
168821+
PreferencesManager.stateManager.set(PREF_NOTIFICATIONS_SHOWN_LIST, shownNotifications);
168822+
currentlyShownNotifications.delete(notificationID);
168823+
}
168824+
168825+
/**
168826+
* Call the server API to acknowledge a notification
168827+
* @param {string} notificationID - The notification ID to acknowledge
168828+
* @returns {Promise<boolean>} - True if successful, false otherwise
168829+
*/
168830+
async function acknowledgeNotificationToServer(notificationID) {
168831+
try {
168832+
const accountBaseURL = LoginService.getAccountBaseURL();
168833+
let url = `${accountBaseURL}/notificationAcknowledged`;
168834+
168835+
const requestBody = {
168836+
notificationID: notificationID
168837+
};
168838+
168839+
let fetchOptions = {
168840+
method: 'POST',
168841+
headers: {
168842+
'Content-Type': 'application/json',
168843+
'Accept': 'application/json'
168844+
},
168845+
body: JSON.stringify(requestBody)
168846+
};
168847+
168848+
// Handle different authentication methods for browser vs desktop
168849+
if (Phoenix.isNativeApp) {
168850+
// Desktop app: use appSessionID and validationCode
168851+
const profile = LoginService.getProfile();
168852+
if (profile && profile.apiKey && profile.validationCode) {
168853+
requestBody.appSessionID = profile.apiKey;
168854+
requestBody.validationCode = profile.validationCode;
168855+
fetchOptions.body = JSON.stringify(requestBody);
168856+
}
168857+
} else {
168858+
// Browser app: use session cookies
168859+
fetchOptions.credentials = 'include';
168860+
}
168861+
168862+
const response = await fetchFn(url, fetchOptions);
168863+
168864+
if (response.ok) {
168865+
const result = await response.json();
168866+
if (result.isSuccess) {
168867+
console.log(`Notification ${notificationID} acknowledged successfully`);
168868+
return true;
168869+
}
168870+
}
168871+
168872+
console.warn(`Failed to acknowledge notification ${notificationID}:`, response.status);
168873+
return false;
168874+
} catch (error) {
168875+
console.error(`Error acknowledging notification ${notificationID}:`, error);
168876+
return false;
168877+
}
168878+
}
168879+
168880+
/**
168881+
* Handle notification dismissal
168882+
* @param {string} notificationID - The notification ID that was dismissed
168883+
*/
168884+
async function handleNotificationDismiss(notificationID) {
168885+
// Always mark as shown locally to prevent re-showing, even if API fails
168886+
markNotificationAsShown(notificationID);
168887+
168888+
// Call server API to acknowledge
168889+
return acknowledgeNotificationToServer(notificationID);
168890+
}
168891+
168892+
/**
168893+
* Check if a notification should be shown
168894+
* @param {Object} notification - The notification object from server
168895+
* @returns {boolean} - True if should be shown, false otherwise
168896+
*/
168897+
function shouldShowNotification(notification) {
168898+
if (!notification || !notification.notificationID) {
168899+
return false;
168900+
}
168901+
168902+
// Check if expired
168903+
if (notification.validTill && Date.now() > notification.validTill) {
168904+
return false;
168905+
}
168906+
168907+
// Check if already shown (persistent storage)
168908+
const shownNotifications = getShownNotifications();
168909+
if (shownNotifications[notification.notificationID]) {
168910+
return false;
168911+
}
168912+
168913+
// Check if currently being shown (in-memory)
168914+
if (currentlyShownNotifications.has(notification.notificationID)) {
168915+
return false;
168916+
}
168917+
168918+
return true;
168919+
}
168920+
168921+
/**
168922+
* Display a single notification
168923+
* @param {Object} notification - The notification object from server
168924+
*/
168925+
function displayNotification(notification) {
168926+
const {
168927+
notificationID,
168928+
title,
168929+
htmlContent,
168930+
options = {}
168931+
} = notification;
168932+
168933+
// Mark as currently showing to prevent duplicates
168934+
currentlyShownNotifications.add(notificationID);
168935+
168936+
// Prepare options for NotificationUI
168937+
const toastOptions = {
168938+
dismissOnClick: options.dismissOnClick !== undefined ? options.dismissOnClick : true,
168939+
toastStyle: options.toastStyle || NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.INFO
168940+
};
168941+
168942+
// Add autoCloseTimeS if provided
168943+
if (options.autoCloseTimeS) {
168944+
toastOptions.autoCloseTimeS = options.autoCloseTimeS;
168945+
}
168946+
168947+
// Create and show the toast notification
168948+
const notificationInstance = NotificationUI.createToastFromTemplate(
168949+
title,
168950+
htmlContent,
168951+
toastOptions
168952+
);
168953+
168954+
// Handle notification dismissal
168955+
notificationInstance.done(() => {
168956+
handleNotificationDismiss(notificationID);
168957+
});
168958+
}
168959+
168960+
/**
168961+
* Clean up stale notification IDs from state manager
168962+
* Removes notification IDs that are no longer in the remote notifications list
168963+
* @param {Array} remoteNotifications - The current notifications from server
168964+
*/
168965+
function cleanupStaleNotifications(remoteNotifications) {
168966+
if (!remoteNotifications || remoteNotifications.length === 0) {
168967+
return;
168968+
}
168969+
168970+
// Build a set of remote notification IDs for quick lookup
168971+
const remoteIDs = new Set();
168972+
for (const notification of remoteNotifications) {
168973+
if (notification.notificationID) {
168974+
remoteIDs.add(notification.notificationID);
168975+
}
168976+
}
168977+
168978+
// Keep only notification IDs that are still in remote notifications
168979+
const shownNotifications = getShownNotifications();
168980+
const updatedShownNotifications = {};
168981+
for (const id in shownNotifications) {
168982+
if (remoteIDs.has(id)) {
168983+
updatedShownNotifications[id] = shownNotifications[id];
168984+
}
168985+
}
168986+
168987+
// Update state if we removed any stale IDs
168988+
const oldCount = Object.keys(shownNotifications).length;
168989+
const newCount = Object.keys(updatedShownNotifications).length;
168990+
if (newCount < oldCount) {
168991+
console.log(`Cleaning up ${oldCount - newCount} stale notification ID(s) from state`);
168992+
PreferencesManager.stateManager.set(PREF_NOTIFICATIONS_SHOWN_LIST, updatedShownNotifications);
168993+
}
168994+
}
168995+
168996+
/**
168997+
* Process notifications from entitlements
168998+
*/
168999+
async function processNotifications() {
169000+
try {
169001+
const notifications = await EntitlementsManager.getNotifications();
169002+
169003+
if (!notifications || !Array.isArray(notifications)) {
169004+
return;
169005+
}
169006+
169007+
// Clean up stale notification IDs if we have at least 1 notification from server
169008+
if (notifications.length > 0) {
169009+
cleanupStaleNotifications(notifications);
169010+
}
169011+
169012+
// Filter and show new notifications
169013+
const notificationsToShow = notifications.filter(shouldShowNotification);
169014+
169015+
if (notificationsToShow.length > 0) {
169016+
console.log(`Showing ${notificationsToShow.length} new notification(s)`);
169017+
notificationsToShow.forEach(displayNotification);
169018+
}
169019+
} catch (error) {
169020+
console.error('Error processing notifications:', error);
169021+
}
169022+
}
169023+
169024+
/**
169025+
* Initialize the UserNotifications service
169026+
*/
169027+
function init() {
169028+
EntitlementsManager = KernalModeTrust.EntitlementsManager;
169029+
LoginService = KernalModeTrust.loginService;
169030+
169031+
if (!EntitlementsManager || !LoginService) {
169032+
throw new Error("UserNotifications requires EntitlementsManager and LoginService in KernalModeTrust");
169033+
}
169034+
169035+
// Listen for entitlements changes
169036+
EntitlementsManager.on(EntitlementsManager.EVENT_ENTITLEMENTS_CHANGED, processNotifications);
169037+
169038+
console.log('UserNotifications service initialized');
169039+
}
169040+
169041+
// Test-only exports for integration testing
169042+
if (Phoenix.isTestWindow) {
169043+
window._test_user_notifications_exports = {
169044+
getShownNotifications,
169045+
markNotificationAsShown,
169046+
shouldShowNotification,
169047+
acknowledgeNotificationToServer,
169048+
processNotifications,
169049+
cleanupStaleNotifications,
169050+
currentlyShownNotifications,
169051+
setFetchFn: function (fn) {
169052+
fetchFn = fn;
169053+
}
169054+
};
169055+
}
169056+
169057+
exports.init = init;
169058+
// no public exports to prevent extension tampering
169059+
});
169060+
168726169061
// SPDX-License-Identifier: AGPL-3.0-only
168727169062
// Copyright (c) 2021 - present core.ai. All rights reserved.
168728169063

0 commit comments

Comments
 (0)