serverless-ircd 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +28 -19
  3. package/apps/cf-worker/package.json +1 -1
  4. package/apps/cf-worker/scripts/smoke.mjs +0 -0
  5. package/apps/cf-worker/src/worker.ts +8 -2
  6. package/apps/cf-worker/tests/smoke.test.ts +1 -0
  7. package/apps/cf-worker/wrangler.test.toml +5 -1
  8. package/apps/cf-worker/wrangler.toml +41 -17
  9. package/apps/local-cli/package.json +1 -1
  10. package/apps/local-cli/src/config-loader.ts +1 -0
  11. package/apps/local-cli/src/main.ts +1 -1
  12. package/apps/local-cli/src/server.ts +28 -2
  13. package/apps/local-cli/tests/e2e.test.ts +52 -0
  14. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  15. package/docs/ADR-002-location-of-authority.md +82 -0
  16. package/docs/ADR-003-durable-object-sharding.md +93 -0
  17. package/docs/ADR-004-dynamodb-schema.md +96 -0
  18. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  19. package/docs/ADR-006-sasl-mechanism-scope.md +86 -0
  20. package/docs/ADR-007-deterministic-ports.md +82 -0
  21. package/docs/ADR-008-monorepo-tooling.md +60 -0
  22. package/docs/AWS-Adapter-Architecture.md +6 -4
  23. package/docs/AWS-Deployment.md +86 -7
  24. package/docs/Cloudflare-Deployment-Guide.md +5 -5
  25. package/docs/Home.md +11 -1
  26. package/docs/Release-Process.md +9 -6
  27. package/package.json +14 -15
  28. package/packages/aws-adapter/package.json +1 -1
  29. package/packages/aws-adapter/src/account-store.ts +121 -0
  30. package/packages/aws-adapter/src/aws-runtime.ts +118 -30
  31. package/packages/aws-adapter/src/cdk-table-defs.ts +5 -1
  32. package/packages/aws-adapter/src/config-loader.ts +30 -0
  33. package/packages/aws-adapter/src/dynamo-account-store.ts +156 -0
  34. package/packages/aws-adapter/src/handlers/default.ts +8 -0
  35. package/packages/aws-adapter/src/handlers/disconnect.ts +27 -8
  36. package/packages/aws-adapter/src/handlers/index.ts +51 -6
  37. package/packages/aws-adapter/src/index.ts +7 -0
  38. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +171 -0
  39. package/packages/aws-adapter/tests/account-store.test.ts +280 -0
  40. package/packages/aws-adapter/tests/config-loader.test.ts +55 -0
  41. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +336 -0
  42. package/packages/aws-adapter/tests/handlers.test.ts +46 -0
  43. package/packages/cf-adapter/package.json +1 -1
  44. package/packages/cf-adapter/src/cf-runtime.ts +42 -22
  45. package/packages/cf-adapter/src/channel-do.ts +40 -1
  46. package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
  47. package/packages/cf-adapter/src/config-loader.ts +29 -0
  48. package/packages/cf-adapter/src/connection-do.ts +85 -0
  49. package/packages/cf-adapter/src/env.ts +27 -1
  50. package/packages/cf-adapter/src/index.ts +2 -1
  51. package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
  52. package/packages/cf-adapter/tests/connection-do.test.ts +40 -0
  53. package/packages/cf-adapter/tests/worker/main.ts +2 -1
  54. package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
  55. package/packages/cf-adapter/wrangler.test.toml +10 -1
  56. package/packages/in-memory-runtime/package.json +1 -1
  57. package/packages/in-memory-runtime/src/in-memory-runtime.ts +14 -28
  58. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +19 -0
  59. package/packages/irc-core/package.json +3 -2
  60. package/packages/irc-core/src/case-fold.ts +64 -0
  61. package/packages/irc-core/src/commands/index.ts +2 -0
  62. package/packages/irc-core/src/commands/invite.ts +6 -9
  63. package/packages/irc-core/src/commands/isupport.ts +1 -1
  64. package/packages/irc-core/src/commands/join.ts +4 -3
  65. package/packages/irc-core/src/commands/kick.ts +4 -11
  66. package/packages/irc-core/src/commands/list.ts +5 -4
  67. package/packages/irc-core/src/commands/mode.ts +5 -9
  68. package/packages/irc-core/src/commands/motd-lines.ts +127 -0
  69. package/packages/irc-core/src/commands/motd.ts +6 -110
  70. package/packages/irc-core/src/commands/oper.ts +151 -0
  71. package/packages/irc-core/src/commands/registration.ts +8 -7
  72. package/packages/irc-core/src/commands/tagmsg.ts +205 -0
  73. package/packages/irc-core/src/config.ts +26 -3
  74. package/packages/irc-core/src/index.ts +2 -0
  75. package/packages/irc-core/src/ports.ts +68 -4
  76. package/packages/irc-core/src/types.ts +22 -0
  77. package/packages/irc-core/tests/account-store.test.ts +88 -0
  78. package/packages/irc-core/tests/case-fold.test.ts +80 -0
  79. package/packages/irc-core/tests/commands/kick.test.ts +15 -0
  80. package/packages/irc-core/tests/commands/oper.test.ts +257 -0
  81. package/packages/irc-core/tests/commands/registration.test.ts +106 -14
  82. package/packages/irc-core/tests/commands/tagmsg.test.ts +688 -0
  83. package/packages/irc-core/tests/commands/who.test.ts +22 -4
  84. package/packages/irc-core/tests/commands/whois.test.ts +8 -1
  85. package/packages/irc-core/tests/config.test.ts +75 -0
  86. package/packages/irc-server/package.json +1 -1
  87. package/packages/irc-server/src/actor.ts +24 -0
  88. package/packages/irc-server/src/routing.ts +9 -0
  89. package/packages/irc-server/tests/actor.test.ts +220 -1
  90. package/packages/irc-server/tests/routing.test.ts +3 -0
  91. package/packages/irc-test-support/package.json +1 -1
  92. package/packages/irc-test-support/src/in-memory-harness.ts +5 -4
  93. package/pnpm-workspace.yaml +1 -0
  94. package/tools/seed-aws-accounts.ts +80 -0
  95. package/AGENTS.md +0 -5
  96. package/MOTD.txt +0 -3
  97. package/PLAN-FIXES.md +0 -358
  98. package/PLAN.md +0 -420
  99. package/dashboards/cloudwatch-irc.json +0 -139
  100. package/progress.md +0 -107
  101. package/tickets.md +0 -2485
  102. package/webircgateway/LICENSE +0 -201
  103. package/webircgateway/Makefile +0 -44
  104. package/webircgateway/README.md +0 -134
  105. package/webircgateway/config.conf.example +0 -135
  106. package/webircgateway/go.mod +0 -16
  107. package/webircgateway/go.sum +0 -89
  108. package/webircgateway/main.go +0 -118
  109. package/webircgateway/pkg/dnsbl/dnsbl.go +0 -121
  110. package/webircgateway/pkg/identd/identd.go +0 -86
  111. package/webircgateway/pkg/identd/rpcclient.go +0 -59
  112. package/webircgateway/pkg/irc/isupport.go +0 -56
  113. package/webircgateway/pkg/irc/message.go +0 -217
  114. package/webircgateway/pkg/irc/state.go +0 -79
  115. package/webircgateway/pkg/proxy/proxy.go +0 -129
  116. package/webircgateway/pkg/proxy/server.go +0 -237
  117. package/webircgateway/pkg/recaptcha/recaptcha.go +0 -59
  118. package/webircgateway/pkg/webircgateway/client.go +0 -741
  119. package/webircgateway/pkg/webircgateway/client_command_handlers.go +0 -495
  120. package/webircgateway/pkg/webircgateway/config.go +0 -385
  121. package/webircgateway/pkg/webircgateway/gateway.go +0 -278
  122. package/webircgateway/pkg/webircgateway/gateway_utils.go +0 -133
  123. package/webircgateway/pkg/webircgateway/hooks.go +0 -152
  124. package/webircgateway/pkg/webircgateway/letsencrypt.go +0 -41
  125. package/webircgateway/pkg/webircgateway/messagetags.go +0 -103
  126. package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +0 -206
  127. package/webircgateway/pkg/webircgateway/transport_sockjs.go +0 -107
  128. package/webircgateway/pkg/webircgateway/transport_tcp.go +0 -113
  129. package/webircgateway/pkg/webircgateway/transport_websocket.go +0 -126
  130. package/webircgateway/pkg/webircgateway/utils.go +0 -147
  131. package/webircgateway/plugins/example/plugin.go +0 -11
  132. package/webircgateway/plugins/stats/plugin.go +0 -52
  133. package/webircgateway/staticcheck.conf +0 -1
  134. package/webircgateway/webircgateway.svg +0 -3
@@ -1,79 +0,0 @@
1
- package irc
2
-
3
- import (
4
- "strings"
5
- "sync"
6
- "time"
7
- )
8
-
9
- type State struct {
10
- LocalPort int
11
- RemotePort int
12
- Username string
13
- Nick string
14
- RealName string
15
- Password string
16
- Account string
17
- Modes map[string]string
18
-
19
- channelsMutex sync.Mutex
20
- Channels map[string]*StateChannel
21
- ISupport *ISupport
22
- }
23
-
24
- type StateChannel struct {
25
- Name string
26
- Modes map[string]string
27
- Joined time.Time
28
- }
29
-
30
- func NewState() *State {
31
- return &State{
32
- Channels: make(map[string]*StateChannel),
33
- ISupport: &ISupport{
34
- tokens: make(map[string]string),
35
- },
36
- }
37
- }
38
-
39
- func NewStateChannel(name string) *StateChannel {
40
- return &StateChannel{
41
- Name: name,
42
- Modes: make(map[string]string),
43
- Joined: time.Now(),
44
- }
45
- }
46
-
47
- func (m *State) HasChannel(name string) (ok bool) {
48
- m.channelsMutex.Lock()
49
- _, ok = m.Channels[strings.ToLower(name)]
50
- m.channelsMutex.Unlock()
51
- return
52
- }
53
-
54
- func (m *State) GetChannel(name string) (channel *StateChannel) {
55
- m.channelsMutex.Lock()
56
- channel = m.Channels[strings.ToLower(name)]
57
- m.channelsMutex.Unlock()
58
- return
59
- }
60
-
61
- func (m *State) SetChannel(channel *StateChannel) {
62
- m.channelsMutex.Lock()
63
- m.Channels[strings.ToLower(channel.Name)] = channel
64
- m.channelsMutex.Unlock()
65
- }
66
-
67
- func (m *State) RemoveChannel(name string) {
68
- m.channelsMutex.Lock()
69
- delete(m.Channels, strings.ToLower(name))
70
- m.channelsMutex.Unlock()
71
- }
72
-
73
- func (m *State) ClearChannels() {
74
- m.channelsMutex.Lock()
75
- for i := range m.Channels {
76
- delete(m.Channels, i)
77
- }
78
- m.channelsMutex.Unlock()
79
- }
@@ -1,129 +0,0 @@
1
- package proxy
2
-
3
- import (
4
- "encoding/json"
5
- "errors"
6
- "io"
7
- "net"
8
- )
9
-
10
- type KiwiProxyState int
11
-
12
- const KiwiProxyStateClosed KiwiProxyState = 0
13
- const KiwiProxyStateConnecting KiwiProxyState = 1
14
- const KiwiProxyStateHandshaking KiwiProxyState = 2
15
- const KiwiProxyStateConnected KiwiProxyState = 3
16
-
17
- type ConnError struct {
18
- Msg string
19
- Type string
20
- }
21
-
22
- func (err *ConnError) Error() string {
23
- return err.Msg
24
- }
25
-
26
- type KiwiProxyConnection struct {
27
- Username string
28
- ProxyInterface string
29
- DestHost string
30
- DestPort int
31
- DestTLS bool
32
- State KiwiProxyState
33
- Conn *net.Conn
34
- }
35
-
36
- func MakeKiwiProxyConnection() *KiwiProxyConnection {
37
- return &KiwiProxyConnection{
38
- State: KiwiProxyStateClosed,
39
- }
40
- }
41
-
42
- func (c *KiwiProxyConnection) Close() error {
43
- if c.State == KiwiProxyStateClosed {
44
- return errors.New("Connection already closed")
45
- }
46
-
47
- return (*c.Conn).Close()
48
- }
49
-
50
- func (c *KiwiProxyConnection) Dial(proxyServerAddr string) error {
51
- if c.State != KiwiProxyStateClosed {
52
- return errors.New("Connection in closed state")
53
- }
54
-
55
- c.State = KiwiProxyStateConnecting
56
-
57
- conn, err := net.Dial("tcp", proxyServerAddr)
58
- if err != nil {
59
- return err
60
- }
61
-
62
- c.Conn = &conn
63
- c.State = KiwiProxyStateHandshaking
64
-
65
- meta, _ := json.Marshal(map[string]interface{}{
66
- "username": c.Username,
67
- "interface": c.ProxyInterface,
68
- "host": c.DestHost,
69
- "port": c.DestPort,
70
- "ssl": c.DestTLS,
71
- })
72
-
73
- (*c.Conn).Write(append(meta, byte('\n')))
74
-
75
- buf := make([]byte, 1024)
76
- bufLen, readErr := (*c.Conn).Read(buf)
77
- if readErr != nil {
78
- (*c.Conn).Close()
79
- c.State = KiwiProxyStateClosed
80
- return readErr
81
- }
82
-
83
- response := string(buf)
84
- if bufLen > 0 && response[0] == '1' {
85
- c.State = KiwiProxyStateConnected
86
- } else {
87
- (*c.Conn).Close()
88
- c.State = KiwiProxyStateClosed
89
-
90
- if bufLen == 0 {
91
- return errors.New("The proxy could not connect to the destination")
92
- }
93
-
94
- switch response[0] {
95
- case '0':
96
- return errors.New("The proxy could not connect to the destination")
97
- case '2':
98
- return &ConnError{Msg: "Connection reset", Type: "conn_reset"}
99
- case '3':
100
- return &ConnError{Msg: "Connection refused", Type: "conn_refused"}
101
- case '4':
102
- return &ConnError{Msg: "Host not found", Type: "not_found"}
103
- case '5':
104
- return &ConnError{Msg: "Connection timed out", Type: "conn_timeout"}
105
- }
106
- }
107
-
108
- return nil
109
- }
110
-
111
- func (c *KiwiProxyConnection) Read(b []byte) (n int, err error) {
112
- if c.State == KiwiProxyStateConnecting || c.State == KiwiProxyStateHandshaking {
113
- return 0, nil
114
- } else if c.State == KiwiProxyStateClosed {
115
- return 0, io.EOF
116
- } else {
117
- return (*c.Conn).Read(b)
118
- }
119
- }
120
-
121
- func (c *KiwiProxyConnection) Write(b []byte) (n int, err error) {
122
- if c.State == KiwiProxyStateConnecting || c.State == KiwiProxyStateHandshaking {
123
- return 0, nil
124
- } else if c.State == KiwiProxyStateClosed {
125
- return 0, io.EOF
126
- } else {
127
- return (*c.Conn).Write(b)
128
- }
129
- }
@@ -1,237 +0,0 @@
1
- package proxy
2
-
3
- import (
4
- "bufio"
5
- "crypto/tls"
6
- "encoding/json"
7
- "fmt"
8
- "io"
9
- "log"
10
- "net"
11
- "strconv"
12
- "sync"
13
- "syscall"
14
- "time"
15
-
16
- "github.com/kiwiirc/webircgateway/pkg/identd"
17
- )
18
-
19
- const (
20
- ResponseError = "0"
21
- ResponseOK = "1"
22
- ResponseReset = "2"
23
- ResponseRefused = "3"
24
- ResponseUnknownHost = "4"
25
- ResponseTimeout = "5"
26
- )
27
-
28
- var identdRpc *identd.RpcClient
29
- var Server net.Listener
30
-
31
- type HandshakeMeta struct {
32
- Host string `json:"host"`
33
- Port int `json:"port"`
34
- TLS bool `json:"ssl"`
35
- Username string `json:"username"`
36
- Interface string `json:"interface"`
37
- }
38
-
39
- func MakeClient(conn net.Conn) *Client {
40
- return &Client{
41
- Client: conn,
42
- }
43
- }
44
-
45
- type Client struct {
46
- Client net.Conn
47
- Upstream net.Conn
48
- UpstreamAddr *net.TCPAddr
49
- Username string
50
- BindAddr *net.TCPAddr
51
- TLS bool
52
- }
53
-
54
- func (c *Client) Run() {
55
- var err error
56
-
57
- err = c.Handshake()
58
- if err != nil {
59
- log.Println(err.Error())
60
- return
61
- }
62
-
63
- err = c.ConnectUpstream()
64
- if err != nil {
65
- log.Println(err.Error())
66
- return
67
- }
68
-
69
- c.Pipe()
70
- }
71
-
72
- func (c *Client) Handshake() error {
73
- // Read the first line - it should be JSON
74
- reader := bufio.NewReader(c.Client)
75
- line, readErr := reader.ReadBytes('\n')
76
- if readErr != nil {
77
- return readErr
78
- }
79
-
80
- var meta = HandshakeMeta{
81
- Username: "user",
82
- Port: 6667,
83
- Interface: "0.0.0.0",
84
- }
85
- unmarshalErr := json.Unmarshal(line, &meta)
86
- if unmarshalErr != nil {
87
- c.Client.Write([]byte(ResponseError))
88
- return unmarshalErr
89
- }
90
-
91
- if meta.Host == "" || meta.Port == 0 || meta.Username == "" || meta.Interface == "" {
92
- c.Client.Write([]byte(ResponseError))
93
- return fmt.Errorf("missing args")
94
- }
95
-
96
- c.Username = meta.Username
97
- c.TLS = meta.TLS
98
-
99
- bindAddr, bindAddrErr := net.ResolveTCPAddr("tcp", meta.Interface+":")
100
- if bindAddrErr != nil {
101
- c.Client.Write([]byte(ResponseError))
102
- return fmt.Errorf("interface: " + bindAddrErr.Error())
103
- }
104
- c.BindAddr = bindAddr
105
-
106
- hostStr := net.JoinHostPort(meta.Host, strconv.Itoa(meta.Port))
107
- addr, addrErr := net.ResolveTCPAddr("tcp", hostStr)
108
- if addrErr != nil {
109
- c.Client.Write([]byte(ResponseUnknownHost))
110
- return fmt.Errorf("remote host: " + addrErr.Error())
111
- }
112
- c.UpstreamAddr = addr
113
-
114
- return nil
115
- }
116
-
117
- func (c *Client) ConnectUpstream() error {
118
- dialer := &net.Dialer{}
119
- dialer.LocalAddr = c.BindAddr
120
- dialer.Timeout = time.Second * 10
121
-
122
- conn, err := dialer.Dial("tcp", c.UpstreamAddr.String())
123
- if err != nil {
124
- response := ""
125
- errType := typeOfErr(err)
126
- switch errType {
127
- case "timeout":
128
- response = ResponseTimeout
129
- case "unknown_host":
130
- response = ResponseUnknownHost
131
- case "refused":
132
- response = ResponseRefused
133
- }
134
-
135
- c.Client.Write([]byte(response))
136
- return err
137
- }
138
-
139
- if identdRpc != nil {
140
- lAddr, lPortStr, _ := net.SplitHostPort(conn.LocalAddr().String())
141
- lPort, _ := strconv.Atoi(lPortStr)
142
- identdRpc.AddIdent(lPort, c.UpstreamAddr.Port, c.Username, lAddr)
143
- }
144
-
145
- if c.TLS {
146
- tlsConfig := &tls.Config{InsecureSkipVerify: true}
147
- tlsConn := tls.Client(conn, tlsConfig)
148
- err := tlsConn.Handshake()
149
- if err != nil {
150
- conn.Close()
151
- c.Client.Write([]byte(ResponseReset))
152
- return err
153
- }
154
-
155
- conn = net.Conn(tlsConn)
156
- }
157
-
158
- c.Upstream = conn
159
- c.Client.Write([]byte(ResponseOK))
160
- return nil
161
- }
162
-
163
- func (c *Client) Pipe() {
164
- wg := sync.WaitGroup{}
165
- wg.Add(2)
166
-
167
- go func() {
168
- io.Copy(c.Client, c.Upstream)
169
- c.Client.Close()
170
- wg.Done()
171
- }()
172
-
173
- go func() {
174
- io.Copy(c.Upstream, c.Client)
175
- c.Upstream.Close()
176
- wg.Done()
177
- }()
178
-
179
- wg.Wait()
180
-
181
- if identdRpc != nil {
182
- lAddr, lPortStr, _ := net.SplitHostPort(c.Upstream.LocalAddr().String())
183
- lPort, _ := strconv.Atoi(lPortStr)
184
- identdRpc.RemoveIdent(lPort, c.UpstreamAddr.Port, c.Username, lAddr)
185
- }
186
- }
187
-
188
- func Start(laddr string) {
189
- srv, err := net.Listen("tcp", laddr)
190
- if err != nil {
191
- log.Fatal(err.Error())
192
- }
193
-
194
- // Expose the server
195
- Server = srv
196
- log.Printf("Kiwi proxy listening on %s", srv.Addr().String())
197
-
198
- identdRpc = identd.MakeRpcClient("kiwiproxy" + laddr)
199
- go identdRpc.ConnectAndReconnect("127.0.0.1:1133")
200
-
201
- for {
202
- conn, err := srv.Accept()
203
- if err != nil {
204
- log.Print(err.Error())
205
- break
206
- }
207
-
208
- c := MakeClient(conn)
209
- go c.Run()
210
- }
211
- }
212
-
213
- func typeOfErr(err error) string {
214
- if err == nil {
215
- return ""
216
- }
217
-
218
- if netError, ok := err.(net.Error); ok && netError.Timeout() {
219
- return "timeout"
220
- }
221
-
222
- switch t := err.(type) {
223
- case *net.OpError:
224
- if t.Op == "dial" {
225
- return "unknown_host"
226
- } else if t.Op == "read" {
227
- return "refused"
228
- }
229
-
230
- case syscall.Errno:
231
- if t == syscall.ECONNREFUSED {
232
- return "refused"
233
- }
234
- }
235
-
236
- return ""
237
- }
@@ -1,59 +0,0 @@
1
- // Google re-captcha package tweaked from http://github.com/haisum/recaptcha
2
-
3
- package recaptcha
4
-
5
- import (
6
- "encoding/json"
7
- "io/ioutil"
8
- "net/http"
9
- "net/url"
10
- "time"
11
- )
12
-
13
- // R type represents an object of Recaptcha and has public property Secret,
14
- // which is secret obtained from google recaptcha tool admin interface
15
- type R struct {
16
- URL string
17
- Secret string
18
- lastError []string
19
- }
20
-
21
- // Struct for parsing json in google's response
22
- type googleResponse struct {
23
- Success bool
24
- ErrorCodes []string `json:"error-codes"`
25
- }
26
-
27
- // VerifyResponse is a method similar to `Verify`; but doesn't parse the form for you. Useful if
28
- // you're receiving the data as a JSON object from a javascript app or similar.
29
- func (r *R) VerifyResponse(response string) bool {
30
- r.lastError = make([]string, 1)
31
- client := &http.Client{Timeout: 20 * time.Second}
32
- resp, err := client.PostForm(r.URL,
33
- url.Values{"secret": {r.Secret}, "response": {response}})
34
- if err != nil {
35
- r.lastError = append(r.lastError, err.Error())
36
- return false
37
- }
38
- defer resp.Body.Close()
39
- body, err := ioutil.ReadAll(resp.Body)
40
- if err != nil {
41
- r.lastError = append(r.lastError, err.Error())
42
- return false
43
- }
44
- gr := new(googleResponse)
45
- err = json.Unmarshal(body, gr)
46
- if err != nil {
47
- r.lastError = append(r.lastError, err.Error())
48
- return false
49
- }
50
- if !gr.Success {
51
- r.lastError = append(r.lastError, gr.ErrorCodes...)
52
- }
53
- return gr.Success
54
- }
55
-
56
- // LastError returns errors occurred in last re-captcha validation attempt
57
- func (r R) LastError() []string {
58
- return r.lastError
59
- }