Created
December 3, 2014 21:15
-
-
Save sritchie/b517d67f9507aca36399 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns racehub.om.facebook | |
(:require [cljs.core.async :as a] | |
[racehub.schema :as rs] | |
[schema.core :as s :include-macros true])) | |
;; ## Utilities | |
(defn prune | |
"Takes a mapping of keys -> new key names and a map and returns a | |
map with nils removed and keys swapped where they're present in the | |
swap map. | |
Behavior's undefined if multiple keys in the mapping map -> the same | |
value, or to keys that are already present in the supplied m." | |
[mapping m] | |
(letfn [(prune-swap [[k v]] (when v [[(mapping k k) v]]))] | |
(into {} (mapcat prune-swap m)))) | |
(def pruned-js (comp clj->js prune)) | |
(defn parse-response | |
"Turns the response into cljs before passing it into the supplied | |
callback." | |
[f] | |
(comp f #(js->clj % :keywordize-keys true))) | |
(s/defn with-callbacks | |
:- {(s/optional-key :callback) (s/=> s/Any s/Any) | |
(s/optional-key :channel) rs/Channel | |
s/Any s/Any} | |
"Adds callback options to the supplied schema." | |
[schema :- {s/Any s/Any}] | |
(assoc schema | |
(s/optional-key :callback) (s/=> s/Any s/Any) | |
(s/optional-key :channel) rs/Channel)) | |
(s/defn parse-callback :- (s/=> s/Any s/Any) | |
"Takes a map with optional channel and callback params and returns a | |
callback that parses the returned JS, calls the callback if present | |
and hits the channel if present." | |
[{:keys [channel callback]} :- (with-callbacks {s/Any s/Any})] | |
(parse-response | |
(fn [resp] | |
(when channel (a/put! channel resp)) | |
(when callback (callback resp))))) | |
;; ## API | |
(s/defschema SDKVersion | |
"Determines which versions of the Graph API and any API dialogs or | |
plugins are invoked when using the .api() and .ui() | |
functions. Valid values are determined by currently available | |
versions, such as 'v2.0'. This is optional; if not supplied, the | |
*sdk-version* variable will override." | |
(s/enum "v1.0" "v2.0" "v2.1" "v2.2")) | |
(def ^:dynamic *sdk-version* | |
"Dynamic variable holding the current global SDK version." | |
"v2.2") | |
(s/defn with-sdk-version | |
"Call the supplied no-arg function in an environment with the | |
supplied SDK version. Explicit SDK versions supplied to the API will | |
override this variable." | |
[v :- SDKVersion f] | |
(binding [*sdk-version* v] | |
(f))) | |
;; ## SDK Initialization | |
(def AppID | |
(s/named s/Str "Your application ID. If you don't have one find it | |
in the App dashboard or go there to create a new app. Defaults to | |
null.")) | |
(s/defschema InitParams | |
"See the reference for info on the boolean parameters: | |
https://developers.facebook.com/docs/javascript/reference/FB.init/v2.2" | |
{(s/optional-key :app-id) AppID | |
(s/optional-key :version) SDKVersion | |
(s/optional-key :cookie?) s/Bool | |
(s/optional-key :status?) s/Bool | |
(s/optional-key :xfbml?) s/Bool | |
(s/optional-key :frictionless-requests?) s/Bool | |
(s/optional-key :hide-flash-callback?) s/Bool}) | |
(s/defn init | |
"Initializes the Facebook SDK. This should be called in the callback | |
supplied to `load-sdk`." | |
[m :- InitParams] | |
(let [swaps {:app-id :appId | |
:cookie? :cookie | |
:status? :status | |
:xfbml? :xfbml | |
:frictionless-requests? :frictionlessRequests | |
:hide-flash-callback? :hideFlashCallback} | |
m (update-in m [:version] #(or % *sdk-version*))] | |
(.init js/FB (pruned-js swaps m)))) | |
(s/defn load-sdk | |
"Takes a callback function. Loads the facebook SDK | |
asynchronously. Okay to call multiple times, though it'll only mount | |
the SDK the first time." | |
([] (load-sdk {})) | |
([init-or-f :- (s/either InitParams (s/=> s/Any))] | |
(let [fb-async-init-cb (if (map? init-or-f) | |
(fn [] (init init-or-f)) | |
init-or-f) | |
doc js/document | |
uid "fb-sdk-cljs"] | |
(when-not (.getElementById doc uid) | |
(-> (.-fbAsyncInit js/window) (set! fb-async-init-cb)) | |
;; attach facebook-sdk. | |
(let [script (. doc (createElement "script"))] | |
(doto script | |
(-> .-id (set! uid)) | |
(-> .-async (set! true)) | |
(-> .-src (set! "//connect.facebook.net/en_US/sdk.js"))) | |
(let [first-js (-> (.getElementsByTagName doc "script") (aget 0)) | |
parent (.-parentNode first-js)] | |
(.insertBefore parent script first-js))))))) | |
;; ## Api Calls | |
(s/defschema Method | |
"This is the HTTP method that you want to use for the API | |
request. Consult the Graph API reference docs to see which method | |
you need to use. Default is :get." | |
(s/enum :get :post :delete)) | |
(s/defschema ApiCall | |
"Parameters for a Facebook Graph API call. For more info, see the docs: | |
https://developers.facebook.com/docs/javascript/reference/FB.api" | |
(with-callbacks | |
{:path s/Str | |
(s/optional-key :method) Method | |
(s/optional-key :params) {s/Any s/Any}})) | |
(s/defn api | |
"Performs an API call to the Facebook Graph API. " | |
[{:keys [path method params] :as m | |
:or {method :get, params {}}} :- ApiCall] | |
(.api js/FB path (name method) params (parse-callback m))) | |
;; ## Login Functions | |
;; ### Login Status | |
(s/defschema AuthResponse | |
{:accessToken s/Str | |
:expiresIn rs/UnixTimestamp | |
:signedRequest s/Str | |
:userID s/Str}) | |
(s/defschema LoginStatusResponse | |
"Facebook response to the login-status request. The response is | |
fully documented here: | |
https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus" | |
(s/either {:status (s/enum "not_authorized" "unknown") | |
:authResponse (s/pred nil?)} | |
{:status (s/eq "connected") | |
:authResponse AuthResponse})) | |
(s/defschema LoginStatus | |
(with-callbacks | |
{(s/optional-key :force?) (s/named s/Bool "The response is | |
sometimes cached by FaceBook's SDK. To force a server lookup, | |
supply `true` for the second parameter.")})) | |
(s/defn login-status | |
"If a callback is provided, it'll be called with the returned | |
instance of LoginStatusResponse. If a channel's provided it'll get | |
that too. (Providing both a channel and a callback is fine.)" | |
[{:keys [force?] :as m} :- LoginStatus] | |
(let [cb (parse-callback m)] | |
(.getLoginStatus js/FB cb (boolean force?)))) | |
(s/defn auth-response :- (s/maybe LoginStatusResponse) | |
[] | |
"Synchronous version of login-status. Returns the | |
LoginStatusResponse object if available, nil otherwise." | |
(-> (.getAuthResponse js/FB) | |
(js->clj :keywordize-keys true))) | |
;; ### Login | |
;; | |
;; Code for managing the actual login process. | |
(def Scope | |
(s/named s/Str "Comma-separated list of extended permissions: | |
https://developers.facebook.com/docs/reference/login/extended-permissions")) | |
(s/defschema LoginOpts | |
"See the facebook docs for more information: | |
https://developers.facebook.com/docs/reference/javascript/FB.login/v2.2" | |
{(s/optional-key :auth-type) (s/eq "rerequest") | |
(s/optional-key :scope) Scope | |
(s/optional-key :return-scopes?) s/Bool | |
(s/optional-key :enable-profile-selector?) s/Bool | |
(s/optional-key :profile-selector-ids) s/Str}) | |
(s/defschema Login | |
(with-callbacks {:opts LoginOpts})) | |
(s/defn login | |
"Calling `login` prompts the user to authenticate your application | |
using the Login Dialog. | |
Calling `login` results in the JS SDK attempting to open a popup | |
window. As such, this method should only be called after a user | |
click event, otherwise the popup window will be blocked by most | |
browsers. Be careful NOT to call this method within a core.async go | |
loop! You need to directly call it in an :on-click callback. | |
Dumps a LoginStatusResponse into the channel." | |
[m :- Login] | |
(let [opts (pruned-js | |
{:auth-type :auth_type | |
:return-scopes? :return_scopes | |
:enable-profile-selector? :enable_profile_selector | |
:profile-selector-ids? :profile_selector_ids} | |
(dissoc m :callback :channel))] | |
(.login js/FB (parse-callback m) opts))) | |
;; ### Logout | |
(s/defschema Logout | |
(with-callbacks {})) | |
(s/defn logout | |
"`logout` will log the user out of both your site and Facebook. You | |
will need to have a valid access token for the user in order to | |
call the function. | |
Calling `logout` will also invalidate the access token that you | |
have for the user, unless you have extended the access token. More | |
info on how to extend the access token here: | |
https://developers.facebook.com/roadmap/offline-access-removal/" | |
[m :- Logout] | |
(.logout js/FB (parse-callback m))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment