@@ -63,6 +63,8 @@ import 'package:solidpod/src/solid/utils/misc.dart' show isUserLoggedIn;
6363/// Convention: 0 = low, 1 = medium, 2 = high
6464///
6565/// Throws [NotLoggedInException] if the user is not authenticated.
66+ /// Throws [RecipientNotReadyException] if the recipient's WebID does not
67+ /// exist or their Pod lacks the notification folder for this app.
6668
6769Future <void > sendNotification ({
6870 required String recipientWebId,
@@ -81,6 +83,36 @@ Future<void> sendNotification({
8183 throw NotLoggedInException ('Unable to retrieve sender WebID' );
8284 }
8385
86+ // Pre-flight checks.
87+
88+ // 1. Verify the recipient's WebID exists (unauthenticated GET to the
89+ // public profile document).
90+
91+ final webIdStatus = await checkWebIdExists (recipientWebId);
92+ if (webIdStatus == ResourceStatus .notExist) {
93+ throw RecipientNotReadyException (
94+ 'The recipient WebID does not exist: $recipientWebId . '
95+ 'Please check the WebID is correct.' ,
96+ );
97+ }
98+
99+ // 2. Verify the recipient has initialised their Pod for this app.
100+ // checkPodInitialised performs an authenticated GET on the recipient's
101+ // shared directory — the same check used in the grant-permissions
102+ // workflow. If it returns false the recipient has never set up the app.
103+
104+ final podReady = await checkPodInitialised (recipientWebId);
105+ if (! podReady) {
106+ throw RecipientNotReadyException (
107+ 'The recipient ($recipientWebId ) has not set up this app on their '
108+ 'Pod. They need to log in to the app first so that the required '
109+ 'folder structure is created, before you can send notifications '
110+ 'to them.' ,
111+ );
112+ }
113+
114+ // Build and send the notification.
115+
84116 final timestamp = DateTime .now ().millisecondsSinceEpoch;
85117
86118 final notification = PodNotification (
@@ -101,17 +133,33 @@ Future<void> sendNotification({
101133
102134 final jsonContent = jsonEncode (notification.toJson ());
103135
104- // debugPrint('[sendNotification] Writing to: $fileUrl');
105-
106136 // POST the notification JSON to the recipient's notification container.
107137 // replaceIfExist: false triggers POST (instead of PUT), which only
108138 // requires Append access on the container — matching the public Append
109139 // ACL configured during POD initialisation.
140+ //
141+ // If the POST still fails (e.g. the notification folder was added in a
142+ // newer app version that the recipient has not yet run), convert the
143+ // error into a RecipientNotReadyException with actionable guidance.
110144
111- await createResource (
112- fileUrl,
113- content: jsonContent,
114- contentType: ResourceContentType .auto,
115- replaceIfExist: false ,
116- );
145+ try {
146+ await createResource (
147+ fileUrl,
148+ content: jsonContent,
149+ contentType: ResourceContentType .auto,
150+ replaceIfExist: false ,
151+ );
152+ } on Exception catch (e) {
153+ final errStr = e.toString ();
154+ if (errStr.contains ('403' ) || errStr.contains ('Forbidden' )) {
155+ throw RecipientNotReadyException (
156+ 'The recipient ($recipientWebId ) does not have a notification '
157+ 'folder for this app. This typically happens when the recipient '
158+ 'has not run the latest version of the app. They need to log in '
159+ 'and update their app setup in their Pod before you can send '
160+ 'notifications to them.' ,
161+ );
162+ }
163+ rethrow ;
164+ }
117165}
0 commit comments