summaryrefslogtreecommitdiff
path: root/src/scm/webid-oidc/client/application.scm
diff options
context:
space:
mode:
Diffstat (limited to 'src/scm/webid-oidc/client/application.scm')
-rw-r--r--src/scm/webid-oidc/client/application.scm245
1 files changed, 162 insertions, 83 deletions
diff --git a/src/scm/webid-oidc/client/application.scm b/src/scm/webid-oidc/client/application.scm
index 6263a82..5185cfb 100644
--- a/src/scm/webid-oidc/client/application.scm
+++ b/src/scm/webid-oidc/client/application.scm
@@ -48,18 +48,21 @@
description result-box thread
result join
+ <authorization-prompt>
+ authorization-uri reason continuation
+
<application-state>
main-account other-accounts client error-messages authorization-prompts running-jobs pages
add-account
choose-account
+ remove-account
+ set-accounts
set-client
fold-authorization-prompts
add-page
set-page-uri
close-page
-
- ->sexp
)
#:declarative? #t)
@@ -70,9 +73,23 @@
(description #:init-keyword #:description #:getter description)
(result-box #:init-thunk (lambda () (make-atomic-box #f)) #:getter result-box))
+(define-method (equal? (x <job>) (y <job>))
+ (and (equal? (description x) (description y))
+ (eq? (result-box x) (result-box y))))
+
(define-method (result (job <job>))
(atomic-box-ref (result-box job)))
+(define-class <authorization-prompt> ()
+ (authorization-uri #:init-keyword #:authorization-uri #:getter authorization-uri)
+ (reason #:init-keyword #:reason #:getter reason)
+ (continuation #:init-keyword #:continuation #:getter continuation))
+
+(define-method (equal? (x <authorization-prompt>) (y <authorization-prompt>))
+ (and (equal? (authorization-uri x) (authorization-uri y))
+ (equal? (reason x) (reason y))
+ (eq? (continuation x) (continuation y))))
+
(define-class <application-state> ()
(main-account
#:init-keyword #:main-account
@@ -95,9 +112,6 @@
#:init-keyword #:error-messages
#:getter error-messages
#:init-value '())
- ;; This is a list of pairs: URI * procedure to call with the
- ;; authorization code. The procedure takes 2 arguments: the code and
- ;; a state, and it returns the updated state.
(authorization-prompts
#:init-keyword #:authorization-prompts
#:getter authorization-prompts
@@ -111,34 +125,32 @@
#:getter pages
#:init-value '()))
-(define-method (->sexp (state <application-state>))
- `(begin
- (use-modules (oop goops) (webid-oidc client application))
- (make <application-state>
- ,@(let ((main-account (main-account state)))
- (if main-account
- `(#:main-account ,(account:->sexp main-account))
- '()))
- #:other-accounts (list ,@(map account:->sexp (other-accounts state)))
- ,@(let ((client (client state)))
- (if client
- `(#:client ,(client:->sexp client))
- '()))
- #:error-messages (list ,@(error-messages state)))))
-
-(define-method (write (state <application-state>) port)
- (pretty-print (->sexp state) port))
+(define-method (equal? (x <application-state>) (y <application-state>))
+ (and (equal? (main-account x) (main-account y))
+ (equal? (other-accounts x) (other-accounts y))
+ (equal? (client x) (client y))
+ (equal? (error-messages x) (error-messages y))
+ (equal? (authorization-prompts x) (authorization-prompts y))
+ (equal? (running-jobs x) (running-jobs y))
+ (equal? (pages x) (pages y))))
(define-method (display (state <application-state>) port)
- (format port "#<<application-state> main-account=~a client=~a error-messages=~a authorization-prompts=~a running-jobs=~a>"
+ (format port "#<<application-state> main-account=~a other-accounts=~a client=~a error-messages=~a authorization-prompts=~a running-jobs=~a>"
(call-with-output-string
(lambda (port)
(display (main-account state) port)))
(call-with-output-string
(lambda (port)
+ (display (map (lambda (acct)
+ (uri->string (account:subject acct)))
+ (other-accounts state))
+ port)))
+ (call-with-output-string
+ (lambda (port)
(display (client state) port)))
(error-messages state)
- (map (match-lambda (((= uri->string uri) . _) uri))
+ (map (lambda (prompt)
+ (uri->string (authorization-uri prompt)))
(authorization-prompts state))
(map description (running-jobs state))))
@@ -151,6 +163,8 @@
(jobs (map result finished)))
(match jobs
(() state)
+ ((#f tl ...)
+ (apply-finished-jobs state tl))
((hd tl ...)
(apply-finished-jobs (hd state) tl)))))))
@@ -159,50 +173,65 @@
(call-with-new-thread
(lambda ()
(let ((tag (make-prompt-tag)))
- (call-with-prompt tag
- (lambda ()
- (parameterize
- ((client:client (client state))
- (account:authorization-process
- (lambda* (uri #:key (reason #f))
- (abort-to-prompt
- tag
- (lambda (continuation)
- ;; This is a state updating function. It just
- ;; registers the continuation as a new
- ;; authorization prompt.
- (lambda (previous-state)
- ;; This code runs in the main thread.
- (define (continue state code)
- (add-job state description
- (lambda ()
- (continuation code))))
- (let ((ret (shallow-clone previous-state)))
- (slot-set! ret 'authorization-prompts
- `((,uri . ,continue)
- ,@(authorization-prompts previous-state)))
- ret)))))))
- (cache:use-cache
- (lambda ()
- (with-exception-handler
- (lambda (exn)
- (let ((msg (if (exception-with-message? exn)
- (exception-message exn)
- (format #f "~s" exn))))
- (abort-to-prompt
- tag
- (lambda (_)
- ;; We won’t continue, but we will show the error message
- (lambda (previous-state)
- (let ((ret (shallow-clone previous-state)))
- (slot-set! ret 'error-messages
- `(,msg ,@(error-messages previous-state)))
- ret))))))
- (lambda ()
- (let ((updater (f)))
- (atomic-box-set! (result-box job) updater))))))))
- (lambda (continuation get-updater)
- (atomic-box-set! (result-box job) (get-updater continuation)))))))
+ (atomic-box-set!
+ (result-box job)
+ ;; The following code returns an updater. The updater takes
+ ;; a previous state and returns a new state.
+ ;;
+ ;; There are 2 possible branches:
+ ;;
+ ;; 1. If everything works, the updater is what’s returned by
+ ;; (f);
+ ;;
+ ;; 2. If (f) calls the authorization process, the updater is
+ ;; a function that adds an authorization prompts to further
+ ;; continue the call in a new job. In that case, continuing
+ ;; yields an updater too, because it is delimited by this
+ ;; prompt.
+ (call-with-prompt tag
+ (lambda ()
+ (parameterize
+ ((client:client (client state))
+ (account:authorization-process
+ (lambda* (uri #:key (reason #f))
+ (abort-to-prompt
+ tag
+ (lambda (continuation)
+ ;; This is a state updating function. It just
+ ;; registers the continuation as a new
+ ;; authorization prompt.
+ (lambda (previous-state)
+ ;; This code runs in the main thread.
+ (define (continue state code)
+ (add-job state description
+ (lambda ()
+ (continuation code))))
+ (let ((ret (shallow-clone previous-state)))
+ (slot-set! ret 'authorization-prompts
+ `(,(make <authorization-prompt>
+ #:authorization-uri uri
+ #:reason reason
+ #:continuation continue)
+ ,@(authorization-prompts previous-state)))
+ ret)))))))
+ (with-exception-handler
+ (lambda (exn)
+ (let ((msg (if (exception-with-message? exn)
+ (exception-message exn)
+ (format #f "~s" exn))))
+ (abort-to-prompt
+ tag
+ (lambda (_)
+ ;; We won’t continue, but we will show the error message
+ (lambda (previous-state)
+ (let ((ret (shallow-clone previous-state)))
+ (slot-set! ret 'error-messages
+ `(,msg ,@(error-messages previous-state)))
+ ret))))))
+ (lambda ()
+ (f)))))
+ (lambda (continuation get-updater)
+ (get-updater continuation)))))))
(let ((ret (shallow-clone state)))
(slot-set! ret 'running-jobs `(,job ,@(running-jobs ret)))
ret)))
@@ -220,14 +249,14 @@
(lambda (previous-state)
(let ((current-main-account (main-account previous-state)))
(if current-main-account
- (if (and (equal? (subject current-main-account) (subject new-account))
- (equal? (issuer current-main-account) (issuer new-account)))
+ (if (and (equal? (account:subject current-main-account) (account:subject new-account))
+ (equal? (account:issuer current-main-account) (account:issuer new-account)))
;; First kind of duplicate
(let ((ret (shallow-clone previous-state)))
(slot-set! ret 'error-messages
`(,(format #f (G_ "You already have an account for ~a issued by ~a and it is currently selected.")
- (uri->string (subject new-account))
- (uri->string (issuer new-account)))))
+ (uri->string (account:subject new-account))
+ (uri->string (account:issuer new-account)))))
ret)
;; The main account slot is already taken, add it to the other accounts
(let check ((other (other-accounts previous-state)))
@@ -239,30 +268,59 @@
`(,new-account ,@(other-accounts previous-state)))
ret))
((existing other ...)
- (if (and (equal? (subject existing) (subject new-account))
- (equal? (issuer existing) (issuer new-account)))
+ (if (and (equal? (account:subject existing) (account:subject new-account))
+ (equal? (account:issuer existing) (account:issuer new-account)))
;; Second kind of duplicate
(let ((ret (shallow-clone previous-state)))
(slot-set! ret 'error-messages
`(,(format #f (G_ "You already have an account for ~a issued by ~a.")
- (uri->string (subject new-account))
- (uri->string (issuer new-account)))))
+ (uri->string (account:subject new-account))
+ (uri->string (account:issuer new-account)))))
ret)
(check other))))))
;; No main account yet
(let ((ret (shallow-clone previous-state)))
(slot-set! ret 'main-account new-account)
- ret)))))))
- state)
+ ret))))))))
(define-method (choose-account (state <application-state>) (account <account:account>))
(let ((ret (shallow-clone state)))
(slot-set! ret 'main-account account)
(slot-set! ret 'other-accounts
(filter (lambda (other)
- (not (equal? other account)))))
+ (not (equal? other account)))
+ `(,(main-account state) ,@(other-accounts state))))
ret))
+(define-method (remove-account (state <application-state>) (account <account:account>))
+ (let ((ret (shallow-clone state)))
+ (let ((main (main-account ret))
+ (other (other-accounts ret)))
+ (let* ((all `(,main ,@other))
+ (kept (filter (lambda (a) (not (equal? a account))) all)))
+ (match kept
+ (()
+ (slot-set! ret 'main-account #f)
+ (slot-set! ret 'other-accounts '()))
+ ((main other ...)
+ (slot-set! ret 'main-account main)
+ (slot-set! ret 'other-accounts other)))))
+ ret))
+
+(define-method (set-accounts (state <application-state>) (accounts <list>))
+ (let ((ret (shallow-clone state)))
+ (match accounts
+ (()
+ (slot-set! ret 'main-account #f)
+ (slot-set! ret 'other-accounts '()))
+ ((main other ...)
+ (slot-set! ret 'main-account main)
+ (slot-set! ret 'other-accounts other)))
+ ret))
+
+(define-method (set-accounts (state <application-state>) (main <account:account>) (other <list>))
+ (set-accounts state `(,main ,@other)))
+
(define-method (set-client (state <application-state>) (client <client:client>))
(let ((ret (shallow-clone state)))
(slot-set! ret 'client client)
@@ -278,46 +336,67 @@
(match prompts
(()
(values seed state))
- (((uri . continue) prompts ...)
- (receive (seed code) (f seed uri)
+ ((prompt prompts ...)
+ (receive (seed code) (f seed (authorization-uri prompt))
(let ((state
;; If I have a code, transform state by calling the
;; continuation function (i.e., add a job that will
;; finish the task). If I don’t, I need to keep this
;; prompt around.
(if code
- (continue state code)
+ ((continuation prompt) state code)
(let ((with-unresolved-prompt (shallow-clone state)))
(slot-set! ret 'authorization-prompts
- `((,uri . ,continue) ,@(authorization-prompts state)))
+ `(,prompt ,@(authorization-prompts state)))
with-unresolved-prompt))))
(iter prompts seed state)))))))
(define-class <page> ()
(identifier #:init-keyword #:identifier #:getter identifier))
+(define-method (equal? (x <page>) (y <page>))
+ (and (equal? (identifier x) (identifier y))))
+
(define-class <new-page> (<page>))
(define-class <page-with-uri> (<page>)
(uri #:init-keyword #:uri #:getter uri))
+(define-method (equal? (x <page-with-uri>) (y <page-with-uri>))
+ (and (equal? (uri x) (uri y))))
+
(define-class <loading-page> (<page-with-uri>))
(define-class <error-page> (<page-with-uri>)
(code #:init-keyword #:code #:getter code)
(reason-phrase #:init-keyword #:reason-phrase #:getter reason-phrase))
+(define-method (equal? (x <error-page>) (y <error-page>))
+ (and (equal? (code x) (code y))
+ (equal? (reason-phrase x) (reason-phrase y))))
+
(define-class <loaded-page> (<page-with-uri>)
(etag #:init-keyword #:etag #:getter etag)
(links #:init-keyword #:links #:getter links))
+(define-method (equal? (x <loaded-page>) (y <loaded-page>))
+ (and (equal? (etag x) (etag y))
+ (equal? (links x) (links y))))
+
(define-class <rdf-page> (<loaded-page>)
(triples #:init-keyword #:triples #:getter triples))
+(define-method (equal? (x <rdf-page>) (y <rdf-page>))
+ (and (equal? (triples x) (triples y))))
+
(define-class <non-rdf-page> (<loaded-page>)
(content-type #:init-keyword #:content-type #:getter content-type)
(content #:init-keyword #:content #:getter content))
+(define-method (equal? (x <non-rdf-page>) (y <non-rdf-page>))
+ (and (equal? (content-type x) (content-type y))
+ (equal? (content x) (content y))))
+
(define-method (add-page (state <application-state>) (identifier <string>))
(let ((ret (shallow-clone state)))
(slot-set! ret 'pages