Open gadenbuie opened 1 year ago
This is great! I've been on the verge of this answer (see my question during a talk on Wednesday asking if anybody has example use cases for session$userData
🤣)
I'll need to make sure I take HttpOnly cookies into account for the implementation (they come in with the request but aren't available in the JS), but I'm PRETTY sure everything you suggest will still apply.
Thank you so much!
Hahahaha I thought I'd thought about this before, and found what steered me away: https://twitter.com/JonTheGeek/status/1579287872762699780 🤣
You're welcome! You mean this comment?
hadleywickham says "You might see advice to use session$userData (to work around NS)... Be wary of such advice..." but I think I have a legit reason to muck around in session. He's talking about modules, but I think maybe cookie handling makes sense there? #RStats #RShiny
I'd argue you have a very valid case here. The cookies are global to the session and are limited to a specific user's session. (They'll even change if the user opens the app in a different browser.) Really, I don't think there's much difference between using session$userData
and finding your way back to the root session 😆
I'm going to edit my answer above, but Winston pointed out to me that when reactive values are set there's already an internal identical()
comparison, so the input to reactive values conversion can be simplified a little bit.
Yeah, I think I scared myself away from it, forgot about it for a while, and then ended up using the .root_session()
hack when I came back to make it work 🙃
Implementation notes (which, it turns out, go on a journey! at the end I think I decided not to implement this, per se):
add_cookie_handlers_server()
, for backward compatibility. Hypothetically I can look for session$userData$cookies
, and auto-upgrade the root session's input$cookies into userData if that hasn't happened already. That means we'll still use the .root_session()
hack, but in one place, and at most once per session.input$cookies
more directly.input$cookies_start
intentionally gets set at the start of the session and doesn't change after that. We don't care about it until cookies get touched, though, so probably load it into that same session$userData$cookies
rv when it's generated. Actually, in theory we can just apply the .is_http_only()
data when that object is created, and then we don't care about those starting cookies anymore (but, since we don't necessarily set that object up right at the start, we still need the separate input to keep track of things).Cookies.get()
in input$cookies
, split it at THAT point into separate values. Be sure to empty out any values that go away (I'll need to check how/whether that side makes sense, but probably).userData$cookies
still make sense? It will need to be an RV where each value is reactive on a different input, but I need to look at all of the inputs to figure out what that means. Sooooo.Maybe .root_session()
will remain the right answer, I just need/want to split things up to avoid over-reactivity?
... But OK, probably having it all in a single cookies input, and then observing that and parsing it out, will work. I should be able to auto create the observer when they first intetact with cookies still, to avoid having to add a function to the server. I think. I'll try it out probably tomorrow!
Thanks for this package! I am using it to implement cookies in fairly complex app and am struggling with 'over-reactivity' also. I am able to follow some of the conversation above, but not all. Has there been any new developments? Thanks!
Thanks for this package! I am using it to implement cookies in fairly complex app and am struggling with 'over-reactivity' also. I am able to follow some of the conversation above, but not all. Has there been any new developments? Thanks!
Great to hear!
I started implementing this, but ran into some hairiness, and then got pulled away to other projects. I've had a couple comments over this way + I'm about to update the shiny app that led me to create this, so I'll be back very soon to make sure I'm not doing anything overly complicated 😊
I'm HOPEFUL that I can clean up quite a lot of code my next time through. I've learned SOOOOO much since the last time I worked on this package...
@mbjohnsonmn See #65 (and #64 where I logged that I need to come back to this). It SHOULD be better behaved now, although it definitely isn't quite how I want it just yet. This fix MIGHT help deal with some of your issues, though!
I caught your talk at ShinyConf2023 (which was great, btw!) and I've been thinking a bit about your callout of needing to find the root
session
object to get cookies to work inside modules.I have an MVP proposal for an alternate solution. Because cookies are global settings specific to the user session, I think
session$userData
is a very appropriate place to store and access the cookie values. The$userData
is described in the docs asIt's also helpfully included in the
session
object available in modules, sosession$userData
can be used everywhere.My proposal, in a nutshell, is this:
input$cookies
list to areactiveValues()
session$userData$cookies
.Technically only the second option is required, but under the current design I believe any observation of an individual cookie will introduce a reactive dependency on all of the cookies. Here's a really small example that shows that updating either of the inputs whose values are stored in the cookies causes any observers accessing a single cookie to update.
Imagine individual cookie values are stored in a
reactiveValues()
list insession$userData
. In that case, users could callget_cookie()
or could directly access a single reactive cookie value without taking a complete dependency oninput$cookies
.In the above example, observing
first_name
won't introduce a dependency onlast_name
. And you don't have to go looking for the ancestor session object,session$userData$cookies
is available everywhere.Here's a proof-of-concept for
add_cookie_handlers_server()
:You could also imagine adding an observer of the reactive values list that would handle calling
set_cookie()
, so that the user (or you) could do something liketo update the cookie through an observer added in
add_cookies_handlers_server()
. (That would probably require similar hacks around comparing the current and new versions of the reactive values list.)Here's a demo app that demonstrates how you'd use this in practice (although you might want to hide
session$userData$cookies
insideget_cookie()
instead of calling it directly like I am here).