Fork me on GitHub
#fulcro
<
2020-10-29
>
vaedasynapse08:10:29

So I may be off my rocker due to my naivate. I’m presently on a quest to combine the fulcro-native and fulcro-rad templates as I want the best of both worlds. My present challenge is trying to figure out the least hacky way to handle the bifurcation required for the /api call vs the /api-native call. Right now what’s seeming to me to be the nicest (please don’t judge me, I’m relatively new to all this still 🙂 ) way of doing it is to modify ring’s ‘wrap-defaults’ function so that it can take multiple configurations and simply apply what it presently does for the one configuration to each configuration. I’m a bit green to try and guess if there are any unwanted outcomes of attempting that. I’m also uncertain if there might not be some other way of going about it that would be as, or less, complex. Any suggestions? I’m frankly a bit trepidatious about tweaking a function in ring. Either maintaining a fork OR trying to propose a change gives me pause. I’m learning everyday, but frankly the capabilities of all my fellow clojurists intimidate me to no end (not because anyone is condescending or bad like that, just because they all seem frightfully competent and I’m never sure I’m up to snuff) Anywho. Anyone’s thoughts? Opinions? Snide remarks 😉 ? I reallly really really am in love with the idea of having RAD and native together. It tickles me something fierce. But wow is it taking me longer than I was hoping it would.

bbss09:10:59

@vaedasynapse I'm not sure what problems you foresee. Are the queries and mutations from your native endpoint different? I don't think you will need two endpoints. If there are details that differ between native and web you have several ways to handle that, e.g. change the request (think query params/namespaced keys/namespaced mutations) or write ring middleware to inject the information based on a header or so. Don't be afraid of ring, the whole point of ring is to write your own middleware functions and enrich the request handling like that. RAD on the back-end mostly just generates auto resolvers for your parser and that probably stays the same between web and native.

zilti13:10:25

How do I apply an fs/mark-complete! to an entire form? I tried using it like (fs/mark-complete!) but that didn't work. And neither did (fs/mark-complete! {:entity-ident [:component/id ::TheComponentName]})

cjmurphy11:10:07

What's the definition of 'work'? One definition would be that the set at :com.fulcrologic.fulcro.algorithms.form-state/complete? is the same as the set at :com.fulcrologic.fulcro.algorithms.form-state/fields . That means that every possible form field is complete, so they should all show validation errors if there is something wrong with them - that's the way to verify w/out looking deeply into app state.

zilti11:10:09

So here's the scenario: I have a form, and with URL params it is possible to prefill some fields. The issue now is that those prefilled form fields won't be checked for validity until the user blurs them manually. My idea was, since this specific form has a checkbox the user has to tick, to just mark the entire form as fs/mark-complete! when the user clicks it. But that doesn't seem to work, at least not using the two approaches I mentioned above.

cjmurphy11:10:29

Oh - fs/mark-complete! is a mutation so you can't just call it like a function, you have to call it from transact. That could be the issue.

(c/transact!! this `[(fs/mark-complete! ~{:entity-ident your-ident})])

zilti11:10:47

Oh I did do that. Though I used transact! instead of transact!!, which works fine for individual fields

cjmurphy11:10:16

Yeah !! is just a performance thing - I just copied some code that happened to use it.

cjmurphy11:10:25

Can you mark complete just one field? I'm looking to see that the transact is changing state somehow, b/c easy to make little mistakes such that the mutation does not happen at all. For my use so far I've always used the fs/mark-complete* variant from within my own mutation. So alternatively you could try that just to mix things up a bit.

zilti12:10:24

I can mark complete individual fields just fine, yes. But I also can mark complete just one field per transact!, interestingly - so if I want to mark multiple as complete, I have to make one transact! for each. I am probably doing something very wrong here.

cjmurphy12:10:34

transact!! might even be wrong here for all I know. Side issue. Can't go wrong by always using transact!.

cjmurphy12:10:03

Seems like it is not picking up the :entity-ident when you pass it.

cjmurphy12:10:12

The mark-complete* is less fiddly. Hard to go wrong when you just pass it app-state (not atom) and the ident.

nivekuil12:10:35

In reply to @￱￱slack￱￱￱￱￱￱t03￱￱r￱￱z￱￱g￱￱p￱￱f￱￱r=2d￱￱u0￱￱d5￱￱r￱￱n0￱_￱s1:nivekuil.comThe mark-complete￱*￱ is less fiddly. Hard to go wrong when you just pass it app-state (not atom) and the ident.I'm pretty sure there is some kind of bug with props tunneling and !!, very similar in effect to https://github.com/fulcrologic/fulcro/issues/431 but not limited to computed props

nivekuil12:10:33

I made a demo here https://github.com/nivekuil/fulcro-template/blob/4e7975a946cdde94e0a909f51ed1ab2280e1e0b1/src/workspaces/app/demo_ws.cljs where the ident passed in as props becomes nil after the first transact!! in the form input. was going to look deeper into why that is but am busy

cjmurphy12:10:13

You can always just directly hook up the source code and put debug statements in. Doesn't take too long with dep.edn projects.

zilti12:10:29

Hmm. Yea I guess I'll give mark-complete* a try then. But I still have to use that inside a transaction, no?

cjmurphy12:10:14

Yes you get to st->st functions via a transact.

cjmurphy12:10:56

The on the end means it is a st-&gt;st function. so here remove-tab takes st and other params. mark-complete* will be the same.

tony.kay16:10:58

@vaedasynapse I assume you’re speaking of auth? (since everything else simply depends on a ui plugin for native). So yes, that need a small bit of work since you don’t have cookies on a phone. What I’ve done is what you are saying: make an /api-native…but I use the exact same everything in that chain except auth. There is no api difference.

tony.kay16:10:27

In one of our apps we only use websockets on a particular mobile app, and there will definitely be cookies when the webapp uses websockets:

(defn mobile?
  "A request is mobile if

  1. The URI is /mobile-api OR
  2. The request has NO cookies AND is for websockets /chsk."
  [{:keys [uri headers] :as req}]
  ;; WARNING: Cookies have not been decoded yet, so you have to use raw headers
  (let [cookie (get headers "cookie")]
    (or
      (= "/mobile-api" uri)
      (and (= "/chsk" uri) (nil? cookie)))))

(defstate middleware
  :start
  (let [defaults-config (:ring.middleware/defaults-config s.config/config)]
    (-> not-found-handler
      wrap-websockets
      (wrap-api "/api")
      (wrap-api "/mobile-api")
      (file-upload/wrap-mutation-file-uploads {})
      (wrap-file-response)
      (server/wrap-transit-params {:opts {:handlers (:read transit-handlers)}})
      (server/wrap-transit-response {:opts {:handlers (:write transit-handlers)}})
      (wrap-html5-routes)
      (blob/wrap-blob-service "/rad-files" s.rad-storage/file-store)
      (wrap-if mobile?
        #(wrap-mobile-defaults % defaults-config)
        #(wrap-browser-defaults % defaults-config)))))

tony.kay16:10:09

where wrap-if is:

tony.kay16:10:13

(defn wrap-if
  "Create a fork in the middleware. If the incoming request-predicate is true, then continue through then-wrapper, else else-wrapper"
  [handler request-predicate then-wrapper else-wrapper]
  (let [then-middleware (then-wrapper handler)
        else-middleware (else-wrapper handler)]
    (fn [req]
      (if (request-predicate req)
        (then-middleware req)
        (else-middleware req)))))

tony.kay16:10:56

(I was pretty delighted with wrap-if , though I’m sure others have thought of it as well)

tony.kay16:10:05

yeah, should have done google search 🙂 (https://github.com/pjlegato/ring.middleware.conditional)…well, it was simple and fun to write anyway

tony.kay16:10:08

and then the only thing left is to toggle the stuff in the branches:

(defn wrap-mobile-defaults
  "Mobile defaults:

  * Turn off ring CSRF and Cookie backed sessions.
  * Use a different session store for our mobile session-key.
  * Hard-reject any session-key with the wrong prefix."
  [handler config]
  (let [wrap-mobile-session (fn [handler]
                              (fn [req]
                                (handler (session-store/ensure-mobile-session-key req))))]
    (-> handler
      (wrap-mobile-session)
      (wrap-reject-web-session)
      (wrap-defaults (-> config
                       (update :security dissoc :anti-forgery)
                       (dissoc :session))))))

vaedasynapse02:10:19

@U0CKQ19AQ Sorry to bother on this again. I’m just trying to get this implemented and I’m not certain on a few parts. For example, for the session-store/ensure-mobile-session-key, is that just you placing the the

session-store          (mem/memory-store)
portion from the native template into a component for mount? Or am I misreading that? And if I’m reading that correctly I believe that, in terms of bringing in the functionality of the native template into the rad template that the session-store/ensure-mobile-session-key and it’s counterpart for browser ensure-browser-session-key would be the same? I’m presuming they are kept seperate for any future contingency where they may need different treatment. Also, I’m assuming that all the ‘wrap-reject-web-session’ is the function carrying out the ‘Hard-reject’ based on the prefix? I’m really hoping I can get this functioning, things generally seem a bit easier when I get out of the weeds of the initial setup and have an, at least marginally functioning system that I can use to get a better feel for how things are working. I’m sure if I had more experience with auth code this would all be so much more obvious. Thanks in advance for any help.

tony.kay18:10:45

I don’t understand your first question. If there’s other code you need to see, just list the names of the functions.

tony.kay16:10:23

for mobile we just send back a special customer header with a token, which the mobile app looks for and stores in SecureStore

tony.kay16:10:58

So, on the mobile client, we modify the http remote middleware like so:

(defn wrap-auth [handler]
  (fn [req]
    (let [session-key (auth/session-key-local-storage)]
      (handler (update req :headers assoc "fsi-authorization" session-key)))))

(def request-middleware
  (-> identity
    (net/wrap-fulcro-request (:write transit-handlers))
    (wrap-auth)))

...

(def fulcro-remote (net/fulcro-http-remote
                                          {:url                 m.app-util/server-url
                                           :response-middleware m.app-util/response-middleware
                                           :request-middleware  m.app-util/request-middleware}))

vaedasynapse16:10:10

This is what I was referring to, this is awesome. Thanks so much! Much better than what I was thinking to do. Thanks Tony and bbss!