Skip to content

Commit 7d08063

Browse files
committed
Clean up linking
- Environment and public key were hardcoded - Static link template wasn't serving anymore, moved to a template - Nicer messages in cli and browser
1 parent 5e9a59b commit 7d08063

File tree

3 files changed

+183
-78
lines changed

3 files changed

+183
-78
lines changed

main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func main() {
7676
log.Fatal(err)
7777
}
7878

79-
linker := plaid_cli.NewLinker(data, client)
79+
linker := plaid_cli.NewLinker(data, client, opts)
8080

8181
linkCommand := &cobra.Command{
8282
Use: "link [ITEM-ID-OR-ALIAS]",

pkg/plaid_cli/linker.go

+182-18
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import (
1212
)
1313

1414
type Linker struct {
15-
Results chan string
16-
Errors chan error
17-
Client *plaid.Client
18-
Data *Data
15+
Results chan string
16+
Errors chan error
17+
Client *plaid.Client
18+
ClientOpts plaid.ClientOptions
19+
Data *Data
1920
}
2021

2122
type TokenPair struct {
@@ -38,7 +39,8 @@ func (l *Linker) Link(port string) (*TokenPair, error) {
3839
}
3940

4041
func (l *Linker) link(port string, serveLink func(w http.ResponseWriter, r *http.Request)) (*TokenPair, error) {
41-
log.Println(fmt.Sprintf("Starting Plaid Link on port %s", port))
42+
log.Println(fmt.Sprintf("Starting Plaid Link on port %s...", port))
43+
4244
go func() {
4345
http.HandleFunc("/link", serveLink)
4446
err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
@@ -47,7 +49,9 @@ func (l *Linker) link(port string, serveLink func(w http.ResponseWriter, r *http
4749
}
4850
}()
4951

50-
open.Run(fmt.Sprintf("http://localhost:%s/link", port))
52+
url := fmt.Sprintf("http://localhost:%s/link", port)
53+
log.Println(fmt.Sprintf("Your browser should open automatically. If it doesn't, please visit %s to continue linking!", url))
54+
open.Run(url)
5155

5256
select {
5357
case err := <-l.Errors:
@@ -71,20 +75,39 @@ func (l *Linker) exchange(publicToken string) (plaid.ExchangePublicTokenResponse
7175
return l.Client.ExchangePublicToken(publicToken)
7276
}
7377

74-
func NewLinker(data *Data, client *plaid.Client) *Linker {
78+
func NewLinker(data *Data, client *plaid.Client, clientOpts plaid.ClientOptions) *Linker {
7579
return &Linker{
76-
Results: make(chan string),
77-
Errors: make(chan error),
78-
Client: client,
79-
Data: data,
80+
Results: make(chan string),
81+
Errors: make(chan error),
82+
Client: client,
83+
ClientOpts: clientOpts,
84+
Data: data,
8085
}
8186
}
8287

8388
func handleLink(linker *Linker) func(w http.ResponseWriter, r *http.Request) {
8489
return func(w http.ResponseWriter, r *http.Request) {
8590
switch r.Method {
8691
case http.MethodGet:
87-
http.ServeFile(w, r, "./static/link.html")
92+
t := template.New("link")
93+
t, _ = t.Parse(linkTemplate)
94+
95+
var env string
96+
switch linker.ClientOpts.Environment {
97+
case plaid.Development:
98+
env = "development"
99+
case plaid.Production:
100+
env = "production"
101+
case plaid.Sandbox:
102+
env = "sandbox"
103+
default:
104+
env = "development"
105+
}
106+
d := LinkTmplData{
107+
PublicKey: linker.ClientOpts.PublicKey,
108+
Environment: env,
109+
}
110+
t.Execute(w, d)
88111
case http.MethodPost:
89112
r.ParseForm()
90113
token := r.Form.Get("public_token")
@@ -101,8 +124,15 @@ func handleLink(linker *Linker) func(w http.ResponseWriter, r *http.Request) {
101124
}
102125
}
103126

104-
type RelinkTemplData struct {
127+
type LinkTmplData struct {
128+
PublicKey string
129+
Environment string
130+
}
131+
132+
type RelinkTmplData struct {
105133
PublicToken string
134+
PublicKey string
135+
Environment string
106136
}
107137

108138
func handleRelink(linker *Linker, publicToken string) func(w http.ResponseWriter, r *http.Request) {
@@ -111,8 +141,22 @@ func handleRelink(linker *Linker, publicToken string) func(w http.ResponseWriter
111141
case http.MethodGet:
112142
t := template.New("relink")
113143
t, _ = t.Parse(relinkTemplate)
114-
d := RelinkTemplData{
144+
145+
var env string
146+
switch linker.ClientOpts.Environment {
147+
case plaid.Development:
148+
env = "development"
149+
case plaid.Production:
150+
env = "production"
151+
case plaid.Sandbox:
152+
env = "sandbox"
153+
default:
154+
env = "development"
155+
}
156+
d := RelinkTmplData{
115157
PublicToken: publicToken,
158+
PublicKey: linker.ClientOpts.PublicKey,
159+
Environment: env,
116160
}
117161
t.Execute(w, d)
118162
case http.MethodPost:
@@ -131,7 +175,26 @@ func handleRelink(linker *Linker, publicToken string) func(w http.ResponseWriter
131175
}
132176
}
133177

134-
var relinkTemplate string = `<html>
178+
var linkTemplate string = `<html>
179+
<head>
180+
<style>
181+
.alert-success {
182+
font-size: 1.2em;
183+
font-family: Arial, Helvetica, sans-serif;
184+
background-color: #008000;
185+
color: #fff;
186+
display: flex;
187+
justify-content: center;
188+
align-items: center;
189+
border-radius: 15px;
190+
width: 100%;
191+
height: 100%;
192+
}
193+
.hidden {
194+
visibility: hidden;
195+
}
196+
</style>
197+
</head>
135198
<body>
136199
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
137200
<script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
@@ -143,11 +206,11 @@ var relinkTemplate string = `<html>
143206
// codes to initialize Link; European countries will have GDPR
144207
// consent panel
145208
countryCodes: ['US'],
146-
env: 'development',
209+
env: '{{ .Environment }}',
147210
// Replace with your public_key from the Dashboard
148-
key: '880bb11f8bc9f3c1d8feb4a348f371',
211+
key: '{{ .PublicKey }}',
149212
product: ['transactions'],
150-
token: "{{ .PublicToken }}",
213+
// Optional, specify a language to localize Link
151214
language: 'en',
152215
onLoad: function() {
153216
// Optional, called when Link loads
@@ -169,6 +232,100 @@ var relinkTemplate string = `<html>
169232
// metadata contains information about the institution
170233
// that the user selected and the most recent API request IDs.
171234
// Storing this information can be helpful for support.
235+
236+
document.getElementById("alert").classList.remove("hidden");
237+
},
238+
onEvent: function(eventName, metadata) {
239+
// Optionally capture Link flow events, streamed through
240+
// this callback as your users connect an Item to Plaid.
241+
// For example:
242+
// eventName = "TRANSITION_VIEW"
243+
// metadata = {
244+
// link_session_id: "123-abc",
245+
// mfa_type: "questions",
246+
// timestamp: "2017-09-14T14:42:19.350Z",
247+
// view_name: "MFA",
248+
// }
249+
}
250+
});
251+
252+
handler.open();
253+
254+
})(jQuery);
255+
</script>
256+
257+
<div id="alert" class="alert-success hidden">
258+
<div>
259+
<h2>All done here!</h2>
260+
<p>You can close this window and go back to plaid-cli.</p>
261+
</div>
262+
</div>
263+
</body>
264+
</html> `
265+
266+
var relinkTemplate string = `<html>
267+
<head>
268+
<style>
269+
.alert-success {
270+
font-size: 1.2em;
271+
font-family: Arial, Helvetica, sans-serif;
272+
background-color: #008000;
273+
color: #fff;
274+
display: flex;
275+
justify-content: center;
276+
align-items: center;
277+
border-radius: 15px;
278+
width: 100%;
279+
height: 100%;
280+
}
281+
.hidden {
282+
visibility: hidden;
283+
}
284+
</style>
285+
</head>
286+
<body>
287+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
288+
<script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
289+
<script type="text/javascript">
290+
(function($) {
291+
var handler = Plaid.create({
292+
clientName: 'plaid-cli',
293+
// Optional, specify an array of ISO-3166-1 alpha-2 country
294+
// codes to initialize Link; European countries will have GDPR
295+
// consent panel
296+
countryCodes: ['US'],
297+
env: '{{ .Environment }}',
298+
// Replace with your public_key from the Dashboard
299+
key: '{{ .PublicKey }}',
300+
product: ['transactions'],
301+
token: "{{ .PublicToken }}",
302+
language: 'en',
303+
onLoad: function() {
304+
// Optional, called when Link loads
305+
},
306+
onSuccess: function(public_token, metadata) {
307+
console.log("onSuccess");
308+
// Send the public_token to your app server.
309+
// The metadata object contains info about the institution the
310+
// user selected and the account ID or IDs, if the
311+
// Select Account view is enabled.
312+
$.post('/link', {
313+
public_token: public_token,
314+
});
315+
},
316+
onExit: function(err, metadata) {
317+
if (err != null) {
318+
console.log(err);
319+
320+
// Handle manual relink when credential is already valid
321+
if (err.error_code == "item-no-error") {
322+
$.post('/link', {
323+
public_token: "{{ .PublicToken }}",
324+
});
325+
}
326+
}
327+
328+
document.getElementById("alert").classList.remove("hidden");
172329
},
173330
onEvent: function(eventName, metadata) {
174331
// Optionally capture Link flow events, streamed through
@@ -188,5 +345,12 @@ var relinkTemplate string = `<html>
188345
189346
})(jQuery);
190347
</script>
348+
349+
<div id="alert" class="alert-success hidden">
350+
<div>
351+
<h2>All done here!</h2>
352+
<p>You can close this window and go back to plaid-cli.</p>
353+
</div>
354+
</div>
191355
</body>
192356
</html>`

static/link.html

-59
This file was deleted.

0 commit comments

Comments
 (0)