Skip to content

Commit e63b8f9

Browse files
committed
feat(notification): add action listener-ready handshake with command permissions
1 parent 72ce538 commit e63b8f9

9 files changed

Lines changed: 112 additions & 5 deletions

File tree

plugins/notification/android/src/main/java/NotificationPlugin.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ class NotificationPlugin(private val activity: Activity): Plugin(activity) {
8282
private lateinit var notificationManager: NotificationManager
8383
private lateinit var notificationStorage: NotificationStorage
8484
private var channelManager = ChannelManager(activity)
85+
private val pendingActionEvents = mutableListOf<JSObject>()
86+
private var isActionListenerReady = false
8587

8688
companion object {
8789
var instance: NotificationPlugin? = null
@@ -96,6 +98,10 @@ class NotificationPlugin(private val activity: Activity): Plugin(activity) {
9698

9799
super.load(webView)
98100
this.webView = webView
101+
synchronized(this) {
102+
pendingActionEvents.clear()
103+
isActionListenerReady = false
104+
}
99105
notificationStorage = NotificationStorage(activity, jsonMapper())
100106

101107
val manager = TauriNotificationManager(
@@ -127,8 +133,18 @@ class NotificationPlugin(private val activity: Activity): Plugin(activity) {
127133
}
128134
val dataJson = manager.handleNotificationActionPerformed(intent, notificationStorage)
129135
if (dataJson != null) {
130-
trigger("actionPerformed", dataJson)
136+
dispatchActionPerformed(dataJson)
137+
}
138+
}
139+
140+
private fun dispatchActionPerformed(payload: JSObject) {
141+
synchronized(this) {
142+
if (!isActionListenerReady) {
143+
pendingActionEvents.add(payload)
144+
return
145+
}
131146
}
147+
trigger("actionPerformed", payload)
132148
}
133149

134150
@Command
@@ -189,6 +205,19 @@ class NotificationPlugin(private val activity: Activity): Plugin(activity) {
189205
invoke.resolve()
190206
}
191207

208+
@Command
209+
fun registerActionListenerReady(invoke: Invoke) {
210+
val pending = JSArray()
211+
synchronized(this) {
212+
isActionListenerReady = true
213+
for (event in pendingActionEvents) {
214+
pending.put(event)
215+
}
216+
pendingActionEvents.clear()
217+
}
218+
invoke.resolveObject(pending)
219+
}
220+
192221
@SuppressLint("ObsoleteSdkInt")
193222
@Command
194223
fun getActive(invoke: Invoke) {

plugins/notification/api-iife.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/notification/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const COMMANDS: &[&str] = &[
88
"is_permission_granted",
99
"register_action_types",
1010
"register_listener",
11+
"register_action_listener_ready",
1112
"cancel",
1213
"get_pending",
1314
"remove_active",

plugins/notification/guest-js/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,18 @@ async function onNotificationReceived(
576576
async function onAction(
577577
cb: (notification: ActionPerformedNotification) => void
578578
): Promise<PluginListener> {
579-
return await addPluginListener('notification', 'actionPerformed', cb)
579+
const listener = await addPluginListener('notification', 'actionPerformed', cb)
580+
try {
581+
const pending = await invoke<ActionPerformedNotification[]>(
582+
'plugin:notification|register_action_listener_ready'
583+
)
584+
for (const notification of pending) {
585+
cb(notification)
586+
}
587+
} catch {
588+
// Older plugin versions and non-Android targets may not implement this command.
589+
}
590+
return listener
580591
}
581592

582593
export type {

plugins/notification/ios/Sources/NotificationHandler.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ public class NotificationHandler: NSObject, NotificationHandlerProtocol {
6666
inputValue = inputType.userText
6767
}
6868

69+
// Payload contract note:
70+
// Keep this structure aligned with Android for `actionPerformed`:
71+
// - `actionId`
72+
// - `inputValue` (optional)
73+
// - `notification` (with at least `id`, optionally `actionTypeId`)
74+
//
75+
// Delivery note:
76+
// This event is emitted as soon as the OS callback arrives. If the JS side has
77+
// not attached `onAction` yet (for example during cold start), the event may be
78+
// missed. Android currently mitigates this with a pending-action queue drained
79+
// by JS after listener registration.
80+
//
81+
// TODO: Consider applying the same queue/drain handshake on iOS for parity.
6982
try? self.plugin?.trigger(
7083
"actionPerformed",
7184
data: ReceivedNotification(
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
"$schema" = "../../schemas/schema.json"
4+
5+
[[permission]]
6+
identifier = "allow-register-action-listener-ready"
7+
description = "Enables the register_action_listener_ready command without any pre-configured scope."
8+
commands.allow = ["register_action_listener_ready"]
9+
10+
[[permission]]
11+
identifier = "deny-register-action-listener-ready"
12+
description = "Denies the register_action_listener_ready command without any pre-configured scope."
13+
commands.deny = ["register_action_listener_ready"]

plugins/notification/permissions/autogenerated/reference.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ It allows all notification related features.
1414
- `allow-notify`
1515
- `allow-register-action-types`
1616
- `allow-register-listener`
17+
- `allow-register-action-listener-ready`
1718
- `allow-cancel`
1819
- `allow-get-pending`
1920
- `allow-remove-active`
@@ -324,6 +325,32 @@ Denies the permission_state command without any pre-configured scope.
324325
<tr>
325326
<td>
326327

328+
`notification:allow-register-action-listener-ready`
329+
330+
</td>
331+
<td>
332+
333+
Enables the register_action_listener_ready command without any pre-configured scope.
334+
335+
</td>
336+
</tr>
337+
338+
<tr>
339+
<td>
340+
341+
`notification:deny-register-action-listener-ready`
342+
343+
</td>
344+
<td>
345+
346+
Denies the register_action_listener_ready command without any pre-configured scope.
347+
348+
</td>
349+
</tr>
350+
351+
<tr>
352+
<td>
353+
327354
`notification:allow-register-action-types`
328355

329356
</td>

plugins/notification/permissions/default.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ permissions = [
1616
"allow-notify",
1717
"allow-register-action-types",
1818
"allow-register-listener",
19+
"allow-register-action-listener-ready",
1920
"allow-cancel",
2021
"allow-get-pending",
2122
"allow-remove-active",

plugins/notification/permissions/schemas/schema.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,18 @@
426426
"const": "deny-permission-state",
427427
"markdownDescription": "Denies the permission_state command without any pre-configured scope."
428428
},
429+
{
430+
"description": "Enables the register_action_listener_ready command without any pre-configured scope.",
431+
"type": "string",
432+
"const": "allow-register-action-listener-ready",
433+
"markdownDescription": "Enables the register_action_listener_ready command without any pre-configured scope."
434+
},
435+
{
436+
"description": "Denies the register_action_listener_ready command without any pre-configured scope.",
437+
"type": "string",
438+
"const": "deny-register-action-listener-ready",
439+
"markdownDescription": "Denies the register_action_listener_ready command without any pre-configured scope."
440+
},
429441
{
430442
"description": "Enables the register_action_types command without any pre-configured scope.",
431443
"type": "string",
@@ -487,10 +499,10 @@
487499
"markdownDescription": "Denies the show command without any pre-configured scope."
488500
},
489501
{
490-
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
502+
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-register-action-listener-ready`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
491503
"type": "string",
492504
"const": "default",
493-
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
505+
"markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-register-action-listener-ready`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`"
494506
}
495507
]
496508
}

0 commit comments

Comments
 (0)