Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session: writable from within Sente's router loop? #62

Closed
danielsz opened this issue Aug 8, 2014 · 8 comments
Closed

Session: writable from within Sente's router loop? #62

danielsz opened this issue Aug 8, 2014 · 8 comments

Comments

@danielsz
Copy link
Collaborator

danielsz commented Aug 8, 2014

So I know that I can read session properties inside Sente's router loop, but can I write to it as well?

For example, how would I go about writing fn-that-writes-and-persists-a-session-property?

(defn event-msg-handler
  [{:as ev-msg :keys [ring-req event ?reply-fn]} _]
  (let [session (:session ring-req)
        uid     (:uid session)
        [id data :as ev] event]

    (println "Event: %s" ev)
    (match [id data]
           [:myapp/event-x _] (fn-that-writes-and-persists-a-session-property session)
    :else
    (do (println "Unmatched event: %s" ev)
        (when-not (:dummy-reply-fn? (meta ?reply-fn))
          (?reply-fn {:umatched-event-as-echoed-from-from-server ev}))))))
@danielsz danielsz changed the title Session: writable? Session: writable from within Sente's event loop? Aug 8, 2014
@danielsz danielsz changed the title Session: writable from within Sente's event loop? Session: writable from within Sente's router loop? Aug 8, 2014
@ptaoussanis
Copy link
Member

Hey Daniel!

Sorry for the delay replying to your email btw, haven't forgotten - just a little swamped atm.

This is a good question; I should actually address it in the README's FAQ.

As with Ring generally, the session you see here is an immutable value so can't be modified directly. You've got a few options:

  1. Write changes directly to your underlying mutable session store. This'll usu. require that you have an appropriate user-id->session-id mapping. You can find some more info on Ring's SessionStore protocol, along with the default memory store implementation here. EDIT: Actually, you wouldn't need a mapping. Ring already includes the cookie value in the Ring request so you can just use that to look up the appropriate state. Morning coffee hasn't kicked in yet ;-)
  2. Rely on Ring's session middleware to do this for you by using a standard HTTP Ring req->response for things that need to modify session state. You won't usually need to modify session state often, so it may be feasible to just use standard Ajax requests in those cases (which can go through Ring's normal session middleware). The example project uses this strategy for the login procedure- it fires off a standard Ajax request for the login form so that the auth (POST) handler can return a new session and have the Ring middleware handle the actual session update.
  3. Setup some kind of user-id->mutable-state indirection so that you store only the :uid in sessions - and some/all other state in (say) a {<uid> <state>} atom that you can bang on at your convenience from handlers, etc. Then when you want a Ring request's state, you'll grab the session uid and then use that to lookup the state in your atom.

Does that make sense?

@danielsz
Copy link
Collaborator Author

Thank you for the elaborate response. There is a lot of food for thought here. I'm going to experiment and report back if I gain any insight. Thanks again!

@ptaoussanis
Copy link
Member

Cool. Anyway leaving this open until I've found some time to add an entry to the README's FAQ. Cheers :-)

@ptaoussanis
Copy link
Member

Closing with 5680a14. Feel free to reopen if you've got any follow-up questions, cheers :-)

@danielsz
Copy link
Collaborator Author

👍

@danielsz
Copy link
Collaborator Author

I'm going to document my experiments on this very subject just for posterity's sake and as a reference for others (and me). The reason why one might need to write to the session in the first place is that sometimes you get user data outside of a ring request response cycle. Social signins and oauth dances are typical situations you might encounter.

1, If possible, prefer to write to the session in a ring request response cycle. As @ptaoussanis wrote: The example project uses this strategy for the login procedure- it fires off a standard Ajax request for the login form so that the auth (POST) handler can return a new session and have the Ring middleware handle the actual session update.

2, As @ptaoussanis wrote, you can actually write changes to the underlying mutable session store.
It goes something like that:

Make sure you have a handle on the session store.

(def session-store (atom {}))
(def site 
  (-> site-defaults
      (assoc :session {:store (ring.middleware.session.memory/memory-store session-store)})))

From the client, send an event that you wish to autologin. Then, in the event handler on the server, you can manipulate the store like so:

(match [event]
           [[:mailer/api {:action :autologin}]] (when-let [data (:access-token-response (:oauth session))] 
                                                  (let [user (api/new-user data)
                                                        key (:value (get cookies "ring-session"))]
                                                    (swap! session-store (fn [session] (assoc-in session [key :uid] (:login_name user))))))
           :else (println "Unmatched event:" event))

Finally, you might need to call (sente/chsk-reconnect! <my-chsk>) on the client.

3, Define Ring middleware to detect oauth credentials in the session, and do the work there.

(defn autologin [app]
  (fn [{session :session :as request} ]
    (if-let [data (:access-token-response (:oauth session))]
      (let [user (api/new-user data)
            session (assoc session :uid (:login_name user))]
        (app (assoc request :session session)))
      (app request))))

Add the middleware to your ring handler:

(def app
  (-> routes 
      (autologin)
      (wrap-defaults site)))

In summary, I think solution 1 and 3 are idiomatic and embrace the ring spirit, solution 2 is a bit hack-y.

I hope this will help future generations.

@ptaoussanis
Copy link
Member

Hey Daniel, updated the README with a link to your comment here (thanks!): https://github.com/ptaoussanis/sente#how-can-server-side-channel-socket-events-modify-a-users-session

@danielsz
Copy link
Collaborator Author

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants