Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2b67c41
WIP: desktop Part B (1/n) — type foundation for the libsession-parsed…
jagerman Jul 19, 2026
8af3078
WIP: desktop Part B (2/n) — relay raw response bytes to libsession, n…
jagerman Jul 19, 2026
1e3ebc7
pro: finish request-side raw-wire relay + single-source url/pubkeys
jagerman Jul 20, 2026
0f324e3
pro: adapt response consumers to flat libsession-parsed structs
jagerman Jul 20, 2026
f3a2459
pro: re-key desktop response handling to the Delta #12 status field
jagerman Jul 20, 2026
8ef5d4a
pro: desktop-B — provider metadata via i18n + on-demand URLs
jagerman Jul 20, 2026
aad2867
Pro: dynamic provider display, {pro_stores} store list, and error_cod…
jagerman Jul 21, 2026
f8c23d8
Pro: rename genIndexHash -> revocationTag (match libsession revocatio…
jagerman Jul 22, 2026
94e0f0d
Pro: send proof expiry as whole seconds on the wire (was milliseconds)
jagerman Jul 22, 2026
0cb446a
Pro: migrate proGenIndexHashB64 column via a new v57, not by editing v50
jagerman Jul 22, 2026
3c8516c
Pro: remove the unused request-state `t` field
jagerman Jul 22, 2026
844c6d5
Pro: don't clear the (synced) proof on an unknown backend status
jagerman Jul 22, 2026
65cf2b3
Pro: guard openProviderUrl against an empty provider URL
jagerman Jul 22, 2026
5e205e7
Pro: version the persisted get-details cache
jagerman Jul 22, 2026
5357c13
Pro: leave a transition reminder for the persisted get-details cache …
jagerman Jul 22, 2026
f6e1da3
Pro: remove hardcoded dev-backend URL/key copies; fail fast instead
jagerman Jul 22, 2026
4d8ffe7
Pro: clarify the isSimpleTokenNoArgs existence check + trim doc comment
jagerman Jul 22, 2026
a3ad394
Pro: rename ProStatus.NeverBeenPro -> Never
jagerman Jul 22, 2026
c7a77a3
Pro: desktop app consumes get_pro_status (was get_pro_details)
jagerman Jul 23, 2026
0eb631e
Pro: purge get_pro_status endpoint naming on desktop (-> ProStatus)
jagerman Jul 23, 2026
e62d7ea
Render plan period generically from {count, unit}
jagerman Jul 23, 2026
b654fa4
Drop stale Delta-N spec citations; reference current § sections
jagerman Jul 23, 2026
70f1658
Localizer: allow trusted HTML args via `htmlArgs` opt-out
jagerman Jul 23, 2026
82031e2
Purge remaining "pro details" naming for the get_pro_status result
jagerman Jul 23, 2026
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
4 changes: 2 additions & 2 deletions protos/SignalService.proto
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,9 @@ message GroupUpdateDeleteMemberContentMessage {

message ProProof {
optional uint32 version = 1;
optional bytes genIndexHash = 2;
optional bytes revocationTag = 2;
optional bytes rotatingPublicKey = 3;
optional uint64 expireAtMs = 4; // Epoch timestamp in milliseconds
optional uint64 expiryUnixTs = 4; // Epoch timestamp in whole seconds (what the Session Pro backend signs over)
optional bytes sig = 5;
}

Expand Down
19 changes: 8 additions & 11 deletions ts/components/dialog/debug/FeatureFlags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isBoolean, isNil } from 'lodash';
import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react';
import { clipboard } from 'electron';
import useAsync from 'react-use/lib/useAsync';
import { ProConfig, type ProProof } from 'libsession_util_nodejs';
import { ProConfig } from 'libsession_util_nodejs';
import { getAppDispatch } from '../../../state/dispatch';
import {
getDataFeatureFlag,
Expand Down Expand Up @@ -740,7 +740,7 @@ function ProConfigForm({
);
const [sigInput, setSigInput] = useState<string>(proConfig?.proProof.signatureHex ?? '');
const [genHashInput, setGenHashInput] = useState<string>(
proConfig?.proProof.genIndexHashB64 ?? ''
proConfig?.proProof.revocationTagB64 ?? ''
);
const [versionInput, setVersionInput] = useState<string>(
proConfig?.proProof.version.toString() ?? ''
Expand Down Expand Up @@ -924,15 +924,12 @@ export const ProDebugSection = ({
if (getFeatureFlag('debugServerRequests')) {
window?.log?.debug('getProProof response: ', response);
}
if (response?.status_code === 200) {
const proProof: ProProof = {
expiryMs: response.result.expiry_unix_ts_ms,
genIndexHashB64: response.result.gen_index_hash_b64,
rotatingPubkeyHex: response.result.rotating_pkey_hex,
version: response.result.version,
signatureHex: response.result.sig_hex,
};
await UserConfigWrapperActions.setProConfig({ proProof, rotatingSeedHex });
if (response && response.status === 'ok') {
// libsession returns a ready-made ProProof; relay it verbatim.
await UserConfigWrapperActions.setProConfig({
proProof: response.proof,
rotatingSeedHex,
});
}
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from 'styled-components';
import { type ReactNode } from 'react';
import { type ReactNode, useEffect, useState } from 'react';
import { getAppDispatch } from '../../../../../state/dispatch';
import { tr } from '../../../../../localization/localeTools';
import { isSimpleTokenNoArgs, tr } from '../../../../../localization/localeTools';
import { Localizer } from '../../../../basic/Localizer';
import { ModalBasicHeader } from '../../../../SessionWrapperModal';
import { ModalBackButton } from '../../../shared/ModalBackButton';
Expand All @@ -16,6 +16,8 @@ import { LucideIcon } from '../../../../icon/LucideIcon';
import { LUCIDE_ICONS_UNICODE, WithLucideUnicode } from '../../../../icon/lucide';
import { SessionButton, SessionButtonColor } from '../../../../basic/SessionButton';
import { showLinkVisitWarningDialog } from '../../../OpenUrlModal';
import { ProWrapperActions } from '../../../../../webworker/workers/browser/libsession_worker_interface';
import type { ProviderUrls } from 'libsession_util_nodejs';
import { proButtonProps } from '../../../SessionCTA';
import { Flex } from '../../../../basic/Flex';
import type { ProNonOriginatingPageVariant } from '../../../../../types/ReduxTypes';
Expand All @@ -33,18 +35,74 @@ type VariantPageProps = {
};

const useProBackendProDetailsLocal = useProBackendProDetails;

/**
* Provider support/management URLs are libsession's (fetched by provider slug), not client display data,
* so we resolve them on demand at click time via the worker rather than threading async state into the
* (synchronous) selector. `pick` chooses which URL from the resolved set.
*/
async function openProviderUrl(
provider: ProPaymentProvider,
pick: (urls: ProviderUrls) => string,
dispatch: Parameters<typeof showLinkVisitWarningDialog>[1]
) {
const urls = await ProWrapperActions.providerUrls({ code: provider });
if (urls) {
showLinkVisitWarningDialog(pick(urls), dispatch);
}
Comment thread
Bilb marked this conversation as resolved.
}
const useCurrentNeverHadProLocal = useCurrentNeverHadPro;

/**
* For some texts, we want `Apple website` for apple but `Google Play Store website` for google...
* Those two are not stored in the same field, so this hook can be used to fetch the right one
*/
function useStoreOrPlatformFromProvider(data: ProcessedProDetails['data']) {
return data.provider === ProPaymentProvider.iOSAppStore
return data.provider === ProPaymentProvider.AppStore
? data.providerConstants.platform // we want `Apple website` for apple
: data.providerConstants.store; // but `Google Play Store website` for google...
}

// The purchasable-platform slug set is a static libsession constant, so fetch it once and cache it.
let cachedVisiblePlatformSlugs: Array<string> | null = null;

/**
* Build the `{pro_stores}` bulleted list from the purchasable provider slugs, keeping only those with a
* `pro_provider_<slug>_store` translation (a new/untranslated provider is skipped gracefully). Desktop
* keeps libsession's order as-is (it has no "own" platform to hoist).
*/
function buildProStoresList(slugs: Array<string>): string {
return slugs
.map(slug => {
const token = `pro_provider_${slug}_store`;
return isSimpleTokenNoArgs(token) ? tr(token) : undefined;
})
.filter((store): store is string => !!store)
.map(store => `<br/>• ${store}`)
.join('');
}

/** The localized `{pro_stores}` list for the "purchase via …" messages (fetched once; empty until loaded). */
function useProStoresList(): string {
const [slugs, setSlugs] = useState<Array<string>>(cachedVisiblePlatformSlugs ?? []);
useEffect(() => {
if (cachedVisiblePlatformSlugs) {
return undefined;
}
let cancelled = false;
void ProWrapperActions.visiblePlatforms().then(result => {
cachedVisiblePlatformSlugs = result;
if (!cancelled) {
setSlugs(result);
}
});
return () => {
cancelled = true;
};
}, []);
return buildProStoresList(slugs);
}

function ProStatusTextUpdate() {
const { data } = useProBackendProDetailsLocal();
return data.autoRenew ? (
Expand Down Expand Up @@ -170,8 +228,8 @@ function ProInfoBlockDevice({ textElement }: { textElement: ReactNode }) {
}

function ProInfoBlockDeviceLinked() {
const { data } = useProBackendProDetailsLocal();
const hasNeverHadPro = useCurrentNeverHadProLocal();
const proStores = useProStoresList();

return (
<ProInfoBlockItem
Expand All @@ -181,8 +239,7 @@ function ProInfoBlockDeviceLinked() {
<strong>{tr('onLinkedDevice')}</strong>
<Localizer
token={hasNeverHadPro ? 'proUpgradeDesktopLinked' : 'proRenewDesktopLinked'}
platform_store={data.providerConstants.store} // this one is always store
platform_store_other={data.providerConstants.store_other}
pro_stores={proStores}
/>
</ProInfoBlockText>
}
Expand Down Expand Up @@ -257,15 +314,14 @@ function ProInfoBlockLayout({

function ProInfoBlockUpgrade() {
const dispatch = getAppDispatch();
const { data } = useProBackendProDetailsLocal();
const proStores = useProStoresList();
return (
<ProInfoBlockLayout
titleElement={tr('proUpgradingTo')}
descriptionElement={
<Localizer
token="proAccessUpgradeDesktop"
platform_store={data.providerConstants.store} // this one is always store
platform_store_other={data.providerConstants.store_other}
pro_stores={proStores}
icon={LUCIDE_ICONS_UNICODE.EXTERNAL_LINK_ICON}
/>
}
Expand Down Expand Up @@ -328,15 +384,15 @@ function ProInfoBlockRenew() {
const dispatch = getAppDispatch();
const { data } = useProBackendProDetailsLocal();
const storeOrPlatform = useStoreOrPlatformFromProvider(data);
const proStores = useProStoresList();

return (
<ProInfoBlockLayout
titleElement={tr('renewingPro')}
descriptionElement={
<Localizer
token="proAccessRenewDesktop"
platform_store={data.providerConstants.store}
platform_store_other={data.providerConstants.store_other}
pro_stores={proStores}
icon={LUCIDE_ICONS_UNICODE.EXTERNAL_LINK_ICON}
/>
}
Expand Down Expand Up @@ -504,15 +560,13 @@ function ProInfoBlockRefund() {
}

switch (data.provider) {
case ProPaymentProvider.iOSAppStore:
case ProPaymentProvider.AppStore:
return <ProInfoBlockRefundIOS />;
case ProPaymentProvider.GooglePlayStore:
case ProPaymentProvider.GooglePlay:
return <ProInfoBlockRefundGooglePlay />;
case ProPaymentProvider.Nil:
case ProPaymentProvider.Rangeproof:
return <ProInfoBlockRefundSessionSupport />;
default:
return assertUnreachable(data.provider, `Unknown pro payment provider: ${data.provider}`);
// Rangeproof, none (''), or an unknown/future provider slug -> the Session-support refund flow.
return <ProInfoBlockRefundSessionSupport />;
}
}

Expand All @@ -532,7 +586,7 @@ function ProInfoBlockRefundRequested() {
</ProInfoBlockRefundTitle>
<ProInfoBlockDescription
onClick={() =>
showLinkVisitWarningDialog(data.providerConstants.refund_status_url, dispatch)
void openProviderUrl(data.provider, u => u.refundStatusUrl, dispatch)
}
style={{ cursor: 'pointer' }}
>
Expand Down Expand Up @@ -575,7 +629,7 @@ function ProPageButtonUpdate() {
{...proButtonProps}
buttonColor={SessionButtonColor.Primary}
onClick={() => {
showLinkVisitWarningDialog(data.providerConstants.update_subscription_url, dispatch);
void openProviderUrl(data.provider, u => u.updateSubscriptionUrl, dispatch);
}}
dataTestId="pro-open-platform-website-button"
>
Expand All @@ -593,7 +647,7 @@ function ProPageButtonCancel() {
{...proButtonProps}
buttonColor={SessionButtonColor.Danger}
onClick={() => {
showLinkVisitWarningDialog(data.providerConstants.cancel_subscription_url, dispatch);
void openProviderUrl(data.provider, u => u.cancelSubscriptionUrl, dispatch);
}}
dataTestId="pro-open-platform-website-button"
>
Expand All @@ -611,10 +665,9 @@ function ProPageButtonRefund() {
{...proButtonProps}
buttonColor={SessionButtonColor.Danger}
onClick={() => {
showLinkVisitWarningDialog(
data.isPlatformRefundAvailable
? data.providerConstants.refund_platform_url
: data.providerConstants.refund_support_url,
void openProviderUrl(
data.provider,
u => (data.isPlatformRefundAvailable ? u.refundPlatformUrl : u.refundSupportUrl),
dispatch
);
}}
Expand Down
2 changes: 1 addition & 1 deletion ts/localization
12 changes: 6 additions & 6 deletions ts/models/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ export class ConversationModel extends Model<ConversationAttributes> {
if (!proProof) {
return false;
}
if (ProRevocationCache.isB64HashEffectivelyRevoked(proProof.genIndexHashB64)) {
if (ProRevocationCache.isB64HashEffectivelyRevoked(proProof.revocationTagB64)) {
// `false` because the proof is not valid (revoked)
return false;
}
Expand All @@ -804,12 +804,12 @@ export class ConversationModel extends Model<ConversationAttributes> {
}

const proDetails = this.dbContactProDetails();
if (!proDetails || !proDetails.proExpiryTsMs || !proDetails.proGenIndexHashB64) {
if (!proDetails || !proDetails.proExpiryTsMs || !proDetails.proRevocationTagB64) {
return false;
}

// make sure that genIndexHash was not revoked first
if (ProRevocationCache.isB64HashEffectivelyRevoked(proDetails.proGenIndexHashB64)) {
// make sure that revocation tag was not revoked first
if (ProRevocationCache.isB64HashEffectivelyRevoked(proDetails.proRevocationTagB64)) {
// `false` because the proof is not valid (revoked)
return false;
}
Expand Down Expand Up @@ -1731,11 +1731,11 @@ export class ConversationModel extends Model<ConversationAttributes> {
) {
return null;
}
const proGenIndexHashB64 = this.get('proGenIndexHashB64');
const proRevocationTagB64 = this.get('proRevocationTagB64');
const proExpiryTsMs = this.get('proExpiryTsMs');
const bitsetProFeatures = this.get('bitsetProFeatures');
return {
proGenIndexHashB64,
proRevocationTagB64,
proExpiryTsMs,
bitsetProFeatures,
};
Expand Down
4 changes: 2 additions & 2 deletions ts/models/conversationAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export interface ConversationAttributes {
*/
bitsetProFeatures?: string;

proGenIndexHashB64?: string;
proRevocationTagB64?: string;
proExpiryTsMs?: number;

triggerNotificationsFor: ConversationNotificationSettingType;
Expand Down Expand Up @@ -181,7 +181,7 @@ export const fillConvoAttributesWithDefaults = (
lastMessageInteractionStatus: null,

bitsetProFeatures: undefined,
proGenIndexHashB64: undefined,
proRevocationTagB64: undefined,
proExpiryTsMs: undefined,

triggerNotificationsFor: 'all', // if the settings is not set in the db, this is the default
Expand Down
Loading
Loading