@@ -6,7 +6,7 @@ import './menu-actions/newChallenge.js';
66import './menu-actions/addWordToDictionary.js' ;
77import './menu-actions/totalReminders.js' ;
88
9- import { Devvit , useInterval , useState } from '@devvit/public-api' ;
9+ import { Devvit , JSONValue , useInterval , useState } from '@devvit/public-api' ;
1010import { DEVVIT_SETTINGS_KEYS } from './constants.js' ;
1111import { isServerCall , omit } from '@hotandcold/shared/utils' ;
1212import { GameMode , HardcoreAccessStatus , WebviewToBlocksMessage } from '@hotandcold/classic-shared' ;
@@ -21,6 +21,15 @@ import { RedditApiCache } from './core/redditApiCache.js';
2121import { sendMessageToWebview } from './utils/index.js' ;
2222import { initPayments , PaymentsRepo } from './payments.js' ;
2323import { OnPurchaseResult , OrderResultStatus , usePayments } from '@devvit/payments' ;
24+ import { useChannel } from '@devvit/public-api' ;
25+
26+ export type PurchasedProductBroadcast = {
27+ payload : {
28+ // user who purchased the product; important because we don't want the broadcast to unlock
29+ // hardcore for all users
30+ userId : string ;
31+ } ;
32+ } ;
2433
2534initPayments ( ) ;
2635
@@ -60,22 +69,55 @@ type InitialState =
6069 hardcoreModeAccess : HardcoreAccessStatus ;
6170 } ;
6271
72+ const PURCHASE_REALTIME_CHANNEL = 'PURCHASE_REALTIME_CHANNEL' ;
73+
6374// Add a post type definition
6475Devvit . addCustomPostType ( {
6576 name : 'HotAndCold' ,
6677 height : 'tall' ,
6778 render : ( context ) => {
79+ // This channel is used to broadcast purchase success events to all instances of the app.
80+ // It's necessary because iOS and Android aggressively cache webviews, which can cause
81+ // the purchase success state to not be reflected immediately in all open instances.
82+ // By broadcasting the event through a realtime channel, we ensure all instances
83+ // update their UI state correctly, even if they're cached.
84+ const purchaseRealtimeChannel = useChannel ( {
85+ name : PURCHASE_REALTIME_CHANNEL ,
86+ onMessage ( msg : JSONValue ) {
87+ const msgCasted = msg as PurchasedProductBroadcast ;
88+ if ( msgCasted . payload . userId === context . userId ) {
89+ sendMessageToWebview ( context , {
90+ type : 'HARDCORE_ACCESS_UPDATE' ,
91+ payload : {
92+ access : { status : 'active' } ,
93+ } ,
94+ } ) ;
95+ }
96+ } ,
97+ onSubscribed : ( ) => {
98+ console . log ( 'listening for purchase success broadcast events' ) ;
99+ } ,
100+ } ) ;
101+ purchaseRealtimeChannel . subscribe ( ) ;
102+
68103 const paymentsRepo = new PaymentsRepo ( context . redis ) ;
69104 const payments = usePayments ( async ( paymentsResult : OnPurchaseResult ) => {
70105 switch ( paymentsResult . status ) {
71106 case OrderResultStatus . Success : {
72107 context . ui . showToast ( `Purchase successful!` ) ;
108+ const access = await paymentsRepo . getHardcoreAccessStatus ( context . userId ! ) ;
73109 sendMessageToWebview ( context , {
74- type : 'PURCHASE_PRODUCT_SUCCESS_RESPONSE' ,
110+ type : 'HARDCORE_ACCESS_UPDATE' ,
111+ payload : {
112+ access,
113+ } ,
114+ } ) ;
115+ void purchaseRealtimeChannel . send ( {
75116 payload : {
76- access : await paymentsRepo . getHardcoreAccessStatus ( context . userId ! ) ,
117+ userId : context . userId ! ,
77118 } ,
78119 } ) ;
120+
79121 break ;
80122 }
81123 case OrderResultStatus . Error : {
0 commit comments