actionable-notifications-subflow-for-ios
actionable-notifications-subflow-for-ios copied to clipboard
How to override action title etc?
Hi
I searched the documentation and unfortunately there is no example of how to replace it. maybe someone will have a moment and help me. Or maybe you know where I can find the exact documentation of the action. My code:
` msg.notificationOverride = {}; msg.notificationOverride.title = msg.push.temat; msg.notificationOverride.subtitle = msg.push.podtemat; msg.notificationOverride.services = msg.push.odbiorca; msg.notificationOverride.message = msg.powiadomienie.calawiadomosc; msg.notificationOverride.interruptionLevel = msg.push.typ;
msg.notificationOverride.action1title = "test1"; return msg; `
https://github.com/sstratoti/actionable-notifications-subflow-for-ios/wiki/Override-Values
Your code above looks OK to me, as long as msg.push is being populated before the function node.
If you throw a debug node into the flow and output the full message object, what is the JSON in the debug window? Alternatively, download the latest code and switch "debug mode" to true and it'll output the debug messages we need to troubleshoot.
Oh wait! You meant the Action Title for the action itself! Sorry, haven't had my coffee yet.
I don't think I have any overrides for those values. But I think I could work that in. Are there any other values related to the action that you need as well?
Please try v0.0.3 and let me know how things go?
For the action[X]Title override functionality, this override will ONLY occur if that action is fully defined in the config. i.e., it must have a default title in the node's config for it to even consider overriding the title.
Sorry for the delay - I haven't had access to my computer for a few days.
Yes, I mean strictly actions and responses to notifications. I understand that if I don't put an override it's like the notification is without action?
I will update and let you know
Unfortunately it does not work :(
function node config before iOS Push:
msg.notificationOverride = {}; msg.notificationOverride.title = msg.push.temat; msg.notificationOverride.subtitle = msg.push.podtemat; msg.notificationOverride.services = msg.push.odbiorca; msg.notificationOverride.message = msg.powiadomienie.calawiadomosc; msg.notificationOverride.interruptionLevel = msg.push.typ; msg.notificationOverride.action1title = msg.push.akcja1; return msg;
debug msg after function node:
I receive a notification but when I click I don't have access to the "Odkurzaj" action, nothing appears
Two things:
First, it's action1Title (note the capital T in Title).
Second, in the iOS notification node, did you set "Debug Mode?" to true? It's from the latest release, it's all the way at the bottom of the config tab for the node. If so, you should see additional debug messages in the debug window without having to setup any debug nodes yourself.
https://github.com/sstratoti/actionable-notifications-subflow-for-ios/wiki/Debugging-and-Troubleshooting
As advised, i changed the name to action1Title and rund debug
but unfortunately it didn't help :(
thanks for your time and help!
Hmm. Instead of a screenshot, can you copy the debug output and paste that into this issue?
If you hover over the top line of the debug output, a small copy to clipboard icon should appear over to the right hand side.
I need the full JSON to try to figure out what's happening.
Alternatively, expand everything that is collapsed in your screenshot and paste in all the screenshots into this issue.
Thanks!
ok, i wanted to do it before but the copies are terribly scattered:
message1:
{"_msgid":"aac65572bb23897b","payload":1684070014613,"topic":"","powiadomienie":{"typ":"push","calawiadomosc":"Czy włączyć odkurzacz? "},"push":{"typ":"active","odbiorca":"powiadom_wszystkich","temat":"Maniek","podtemat":"","akcja1":"Odkurzaj"},"wiadomosc":{"wiadomosc1":"Czy włączyć odkurzacz?","wiadomosc2":"","wiadomosc3":"","wiadomosc4":"","wiadomosc5":"","wiadomosc6":""},"_event":"node:3b0c384824ba14c4","notificationOverride":{"title":"Maniek","subtitle":"","services":"powiadom_wszystkich","message":"Czy włączyć odkurzacz? ","interruptionLevel":"active","action1Title":"Odkurzaj"}}
message 2:
{"_msgid":"aac65572bb23897b","payload":{"domain":"notify","data":{"title":"Maniek","message":"Czy włączyć odkurzacz? ","data":{"push":{"sound":{"name":"default"},"interruption-level":"active"},"tag":"_JROCD","group":"None"}},"service":"powiadom_wszystkich"},"topic":"","powiadomienie":{"typ":"push","calawiadomosc":"Czy włączyć odkurzacz? "},"push":{"typ":"active","odbiorca":"powiadom_wszystkich","temat":"Maniek","podtemat":"","akcja1":"Odkurzaj"},"wiadomosc":{"wiadomosc1":"Czy włączyć odkurzacz?","wiadomosc2":"","wiadomosc3":"","wiadomosc4":"","wiadomosc5":"","wiadomosc6":""},"_event":"node:3b0c384824ba14c4","notificationOverride":{"title":"Maniek","subtitle":"","services":"powiadom_wszystkich","message":"Czy włączyć odkurzacz? ","interruptionLevel":"active","action1Title":"Odkurzaj"},"_originalPayload":1684070014613}
This might help reduce the noise from other debug outputs
These options can help to target just the debug output from the current flow in view, or current nodes selected.
So for your config, do you have anything set in action 1 in the iOS node? You have to have something set there as a default in order to override it.
I'm seeing a "title" in your debug, but nothing for the actions. If you press and hold the notification, do any actions show up?
Problem solved :) - I had nothing entered in ios node. When I entered a value there, I could replace it with my own! Cool! Thank you very much
Is it then possible to display one action or two depending on the needs? I'm asking because I have one ios node that handles all notifications and just overwrites the variables
Hmmm I'll have to think on this. Would require a more extensive updating of the function node, but I think it's a worthwhile enhancement. Let me get back to you.
You asked earlier if I wanted to change any more values. I tried to replace the image value a moment ago, unfortunately it didn't work, is there such an option?
"msg.notificationOverride.imagePath = msg.push.image;"
Not for image, but I could add that to the list.
Hi @aLAN-LDZ - currently working on updating this flow this week. I'm planning to implement the following updates to override.
When using the iOS Actionable Notification node, you can dynamically override various properties by setting the msg.notificationOverride
object. Below is a complete sample that demonstrates how to override each possible value, including the newly added properties.
Sample Code for Overrides
// Set msg overrides
### Documentation Sample for Override Properties
msg.notificationOverride = {};
// Basic Notification Information
msg.notificationOverride.title = "Dynamic Title!";
msg.notificationOverride.subtitle = "Dynamic Subtitle";
msg.notificationOverride.message = "Battery is at " + msg.payload.attributes.battery + "%!! Replace it soon!";
msg.notificationOverride.url = "/hacs";
msg.notificationOverride.services = "my_phone, partners_phone, kids_phone"; // Define which devices to send the notification to.
msg.notificationOverride.cameraEntity = "camera.my_back_yard"; // must be a camera entity.
msg.notificationOverride.interruptionLevel = "time-sensitive"; //must be one of the following: passive, active, time-sensitive or critical
msg.notificationOverride.customSound = "custom_sound.wav"; // Custom sound for notification
msg.notificationOverride.group = "family_notifications"; // Group notifications for easy management on the device
// Tag Override (for de-duplication or grouping)
msg.notificationOverride.tag = "xyzTag"; // Use the same tag to overwrite the previous notification or a new tag to send it as a new one.
// Action Properties Overrides - ...repeat as necessary for actions 2 through 4, changing the number in the action's property.
msg.notificationOverride.action1Title = "New Action Title for Action 1!";
msg.notificationOverride.action1ActivationMode = "foreground"; // Options: "foreground", "background"
msg.notificationOverride.action1Uri = "https://www.google.com"; // URI for the action, can also be relative to HA such as /lovelace/myDashboard
msg.notificationOverride.action1TextInputButtonTitle = "Reply"; // may not work with later versions of iOS
msg.notificationOverride.action1TextInputPlaceholder = "Type your response here..."; // may not work with later versions of iOS
msg.notificationOverride.action1AuthenticationRequired = true; // Requires authentication before performing the action
msg.notificationOverride.action1Destructive = true; // Indicates a destructive action (will show option in Red)
msg.notificationOverride.action1Behavior = "default"; // Behavior of the action (e.g., "default", "textInput")
msg.notificationOverride.action1Icon = "sfsymbols:bell.slash"; // Icon associated with the action - check companion documentation for available icons.
// Map Information Overrides
msg.notificationOverride.latitudeFirst = "40.712776";
msg.notificationOverride.longitudeFirst = "-74.005974";
msg.notificationOverride.latitudeSecond = "34.052235";
msg.notificationOverride.longitudeSecond = "-118.243683";
msg.notificationOverride.showLineBetweenPoints = true; // Show a line between the two points on the map
msg.notificationOverride.showCompass = true; // Show compass on the map
msg.notificationOverride.showPointsOfInterest = false; // Show points of interest on the map
msg.notificationOverride.showScale = true; // Show scale on the map
msg.notificationOverride.showTraffic = false; // Show traffic information on the map
msg.notificationOverride.showUserLocation = true; // Show user location on the map
// Media Information Overrides
msg.notificationOverride.contentUrl = "https://example.com/image.jpg"; // URL for the content (image, video, etc.)
msg.notificationOverride.imagePath = "/local/images/picture.jpg"; // Local path for image
msg.notificationOverride.videoPath = "/local/videos/video.mp4"; // Local path for video
msg.notificationOverride.audioPath = "/local/audio/alert.mp3"; // Local path for audio
msg.notificationOverride.lazyLoading = true; // Enable lazy loading for the media content
msg.notificationOverride.hideThumbnail = true; // Hide the thumbnail for the attachment
return msg;
Explanation of Override Properties
-
Basic Notification Information:
-
title
: Override the title of the notification. -
subtitle
: Override the subtitle of the notification. -
message
: Override the main message body. -
url
: Override the URL that opens when the notification is clicked. -
services
: Specify which devices should receive the notification. -
cameraEntity
: The camera entity to display in the notification. -
interruptionLevel
: Define the interruption level, e.g., "time-sensitive". -
customSound
: Custom sound file to play when the notification is received. You need to load in custom sounds into the app. See Companion documentation for more information. -
group
: Group notifications under a specific group tag. Groups like messages in notification center.
-
-
Tag Override:
-
tag
: Use this to control whether the notification overwrites a previously sent notification with the same tag or appears as a new notification.
-
-
Action Properties:
-
action1Title
: Title for the first action button. -
action1ActivationMode
: Mode for the action (foreground or background). -
action1Uri
: URI to open when the action is triggered. -
action1TextInputButtonTitle
: Title for the text input button. -
action1TextInputPlaceholder
: Placeholder text for the text input. -
action1AuthenticationRequired
: Boolean to require authentication before the action. -
action1Destructive
: Boolean to mark the action as destructive. -
action1Behavior
: Behavior of the action, such as default or text input. -
action1Icon
: Icon for the action.
-
...repeat as necessary for actions 2 through 4.
-
Map Information:
-
latitudeFirst
,longitudeFirst
: Coordinates for the first point on the map. -
latitudeSecond
,longitudeSecond
: Coordinates for the second point on the map. -
showLineBetweenPoints
: Boolean to show a line between the two points. -
showCompass
: Boolean to show the compass. -
showPointsOfInterest
: Boolean to show points of interest on the map. -
showScale
: Boolean to show the map scale. -
showTraffic
: Boolean to show traffic information. -
showUserLocation
: Boolean to show the user's location.
-
-
Media Information:
-
contentUrl
: URL for the content to be displayed in the notification. -
imagePath
: Local path for an image to be displayed. -
videoPath
: Local path for a video to be displayed. -
audioPath
: Local path for audio to be played. -
lazyLoading
: Boolean to enable lazy loading for the media content. -
hideThumbnail
: Boolean to hide the thumbnail for the attachment.
-
How to Use the Overrides
Place a function node before the iOS Actionable Notification node, and copy the above code into the function node. This will allow you to dynamically set or override notification properties based on your specific requirements at runtime.
Also, you'll be able to dynamically send in how many actions you want to appear. You won't be required to set values in the node any longer. It'll just check if the override has a title, if not - it'll check if the action in the notification properties has a title, if not, it should skip over that action.
There will still be 4 overall actions. So if you assign a title to actions 1, 3 and 4, and leave action 2's title blank in the config of the node, it'll present 3 buttons for actions and then route to the appropriate output. If you do an override for actions 1 and 3, it'll present 2 buttons, and will route to outputs 1 and 3 when either of those buttons are selected. Hope that makes sense!
Makes sense, this will enable sending actions and not have to send all of them or keeping multiple instances of the subflow just for different number of actions. I suggest that while you are at it to increase the number of actions to 8.
Oh wow, for some reason I thought I was limited to 4 actions, but on the companion website it says we can have up to 10. I'll just do that then. Adding all those fields to the config of the node is...gonna take a while though. But I'll slog through it.
Let us know if we can do manual labor in some way to help out.
I suggest you use just 1 JSON field for each action.
Call it Action1 JSON, Action 2 JSON...
The default value should be:
{ title: null, url: null, activation_mode: null, //can be "foreground"/"background" authentication_required: false, destructive: false }
Most people who use your subflow can fill a JSON field.
This will shorten the UI and make it easier to understand.
It's not a bad idea, I just personally have over 67 flows with this notification subflow attached. If I make a breaking change like that, then when people replace the subflow, they'll lose all their settings. I'm thinking that we save something like that for a v2 of the subflow. This way they could import the updated version, and convert over time. Or maybe Node-Red will allow better organization of subflow inputs by then...
I forked your subflow some time ago (using it without the ui, so unfortunately I can't merge back)
If you are redoing it, may I suggest reorganizing your code a bit so it's easier to maintain? First thing you need to create is a message data model. It is a JSON, very similar in structure to notificationOverride. First function node in the subflow just builds this JSON, either from env.get() params or from msg.notificationOveride. The next function node continues as is, working with the JSON you created. This will clean the rest of your code substantially and will allow you to create another version of this subflow with a different UI.
This is a great idea, much cleaner. I've reworked the code into the following, but I think you're right. It'd be easier to read and understand if I don't try to do everything at once when building the service call.
msg._originalPayload = msg.payload;
const now = Date.now();
const getEnv = key => env.get(key) || ''; // Helper to fetch env values
const safeString = str => str.replace(/[^\w\s]/gi, '').replace(/\s+/g, '_').toUpperCase(); // Sanitization
let flow_msg_variables = {
tag: '',
service: '',
message: msg,
date_created: now
}
// Extract overrides or fallback to env values
const override = msg.notificationOverride ?? {};
const xTitle = override.title ?? getEnv('title');
const xSubtitle = override.subtitle ?? getEnv('subtitle');
const xMessage = override.message ?? getEnv('message');
const xUrl = override.url ?? getEnv('notificationUrl');
const xServices = override.services ?? getEnv('service');
const xCameraEntity = override.cameraEntity ?? getEnv('cameraEntity');
const xInterruptionLevel = override.interruptionLevel ?? getEnv('interruptionLevel');
const xCustomSound = override.customSound ?? getEnv('customSound');
const xGroup = override.group ?? getEnv('group');
// Map Information
const xLatitudeFirst = override.latitudeFirst ?? getEnv('latitudeFirst');
const xLongitudeFirst = override.longitudeFirst ?? getEnv('longitudeFirst');
const xLatitudeSecond = override.latitudeSecond ?? getEnv('latitudeSecond');
const xLongitudeSecond = override.longitudeSecond ?? getEnv('longitudeSecond');
// Media Information
const xContentUrl = override.contentUrl ?? getEnv('contentUrl');
const xImagePath = override.imagePath ?? getEnv('imagePath');
const xVideoPath = override.videoPath ?? getEnv('videoPath');
const xAudioPath = override.audioPath ?? getEnv('audioPath');
const xLazyLoading = override.lazyLoading ?? getEnv('lazyLoading');
const xHideThumbnail = override.hideThumbnail ?? getEnv('hideThumbnail');
// Tag setup
flow_msg_variables.tag = safeString(override.tag ?? getEnv('tag')) || `${safeString(getEnv('title'))}_${flow.get('random')}`;
flow_msg_variables.service = xServices;
// Process messages array
let all_flow_messages = flow.get('flow_messages') || [];
let new_flow_messages = all_flow_messages.filter(msg =>
!(msg.tag === flow_msg_variables.tag && msg.service === flow_msg_variables.service) &&
(now - msg.date_created < 86400000) // keep messages less than 24h old
);
new_flow_messages.push(flow_msg_variables);
flow.set('flow_messages',new_flow_messages);
// Services check
if (!xServices.trim()) {
node.status({ text: 'no services defined', shape: 'ring', fill: 'red' });
return;
} else if (xServices.trim() === "NOONE") {
node.status({ text: 'No one to send to', shape: 'ring', fill: 'yellow' });
return;
}
// Actions setup
const actions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => {
const title = override[`action${i}Title`] ?? getEnv(`action${i}Title`);
if (!title) return null;
const actionObj = {
action: safeString(title),
title
};
// Conditionally add properties if they are not empty
const properties = [
'activationMode', 'uri', 'textInputButtonTitle',
'textInputPlaceholder', 'authenticationRequired',
'destructive', 'behavior', 'icon'
];
properties.forEach(prop => {
const value = override[`action${i}${prop.charAt(0).toUpperCase() + prop.slice(1)}`] ?? getEnv(`action${i}${prop.charAt(0).toUpperCase() + prop.slice(1)}`);
if (value) {
actionObj[prop] = value;
}
});
return actionObj;
}).filter(Boolean);
// Create msg object
msg.payload = {
data: {
title: xTitle,
message: xMessage,
data: {
tag: flow_msg_variables.tag,
...(actions.length > 0 && {actions}),
...(actions.length > 0 && {
action_data: { tag: flow_msg_variables.tag }
}),
...(xUrl && { url: xUrl }),
...(xSubtitle && { subtitle: xSubtitle }),
...(xCameraEntity && { entity_id: xCameraEntity }),
...(xGroup && { group: xGroup }),
push: {
sound: {
name: xCustomSound || getEnv('customSoundPreInstalled') || 'default',
...(env.get('isCriticalNotification') && { critical: 1, volume: 1.0 })
},
...(xInterruptionLevel && { "interruption-level": xInterruptionLevel })
}
}
}
};
// Map Information
if (xLatitudeFirst && xLongitudeFirst) {
msg.payload.data.data.action_data = {
...(actions.length > 0 && { tag: flow_msg_variables.tag }),
latitude: xLatitudeFirst,
longitude: xLongitudeFirst,
...(xLatitudeSecond && xLongitudeSecond && {
second_latitude: xLatitudeSecond,
second_longitude: xLongitudeSecond,
shows_line_between_points: override.showLineBetweenPoints ?? getEnv('showLineBetweenPoints'),
shows_compass: override.showCompass ?? getEnv('showCompass'),
shows_points_of_interest: override.showPointsOfInterest ?? getEnv('showPointsOfInterest'),
shows_scale: override.showScale ?? getEnv('showScale'),
shows_traffic: override.showTraffic ?? getEnv('showTraffic'),
shows_user_location: override.showUserLocation ?? getEnv('showUserLocation')
})
};
}
// Media Information
if (xContentUrl || xImagePath || xVideoPath || xAudioPath) {
msg.payload.data.data = {
...msg.payload.data.data,
contentUrl: xContentUrl || undefined,
image: xImagePath || undefined,
video: xVideoPath || undefined,
audio: xAudioPath || undefined,
lazy: xLazyLoading || undefined,
attachment: xHideThumbnail ? { 'hide-thumbnail': xHideThumbnail } : undefined
};
}
// Send notifications
xServices.trim().split(/,\s*/).forEach(service => {
if (!service) return;
msg.payload.action = `notify.${service}`;
node.send(msg);
});
node.done();
Creating the additional inputs wasn't too bad. I exported the subflow and then copy/paste/replaced for the additional action envs.
Edit: Updated to remove some empty strings from the actions being passed in. An empty string URI was causing the foreground app behavior, even though it was empty.
Took another look at this over lunch, I started to split out the msg from the service call. I realized that if I create a basic msg object, I then have to convert it into the format of the msg for the notification call anyway. And then make the multiple service calls. Thinking of just splitting the service call bit out, and leaving the code above as is?
Also, it may make more sense in a v2 to create an inbound msg object with a bit more depth in structure. Like:
msg.notificationOverride = {
title: "dynamic title!",
subtitle: "dynamic subtitle",
message: "Battery is at 50%!! Replace it soon!",
url: "/hacs",
services: "my_phone, partners_phone, kids_phone",
cameraEntity: "camera.my_back_yard",
interruptionLevel: "time-sensitive",
tag: "xyzTag",
actions: [
{
title: "New action Title for action 1!",
activationMode: "foreground",
uri: "/example",
textInputButtonTitle: "Reply",
textInputPlaceholder: "Type here...",
authenticationRequired: true,
destructive: false,
behavior: "default",
icon: "mdi:alarm"
},
{
title: "Another action title for action 2",
activationMode: "background",
uri: "/another-example",
// Additional properties...
}
// Add more actions as needed
]
};
But at what point are we just becoming an overly complex wrapper for the service call?
If someone is only interested in simple, one way, messages, a service call would work great. However I think you underestimate the real value of your subflow. In it's core, it allows a user to visually attach actions to notifications in NodeRed. The power of your subflow is adding an output node to an action. doing so with service calls would be a lot more complicated.
I like your suggestion for V2, One thing that's missing is actionOutput for each action.
actions: [
{
title: "New action Title for action 1!",
actionOutput:1,
activationMode: "foreground",
uri: "/example",
textInputButtonTitle: "Reply",
textInputPlaceholder: "Type here...",
authenticationRequired: true,
destructive: false,
behavior: "default",
icon: "mdi:alarm"
},
{
title: "Another action title for action 3",
actionOutput:3,
activationMode: "background",
uri: "/another-example",
// Additional properties...
}
// Add more actions as needed
]
For some scenarios, you don't want to send all actions to the user, you noticed how I skipped action 2? this means that the user gets to see only two actions, but if he chooses the second option, the flow will continue from the third output.
True, true. It's also keeping track of which events flow to which node across all flows. You're right.
Also, great call out on the action number. Thanks! I'm going to create a new issue for this so that we capture these ideas for v2.
New version is up, please review the readme/wiki as the notificationOverride has received a big boost in options!
@aLAN-LDZ - this should hopefully resolve your enhancement request. Thanks!