foobara-typescript-remote-command-generator 0.0.14 → 0.0.16

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d21e54189d68b7b93ec70507e3b0fa433a4a8a380fbbd4e1df92031aea3310d2
4
- data.tar.gz: a465fae839c9f38e29908ef3145b9d4b4c0d1de4e37d004893e54653ee783c15
3
+ metadata.gz: f71e4e6218587eee34596a8ce72c35e41e17da8ae5a4740f2a847c5a91a01494
4
+ data.tar.gz: ea0628600beba45ad19318a586bb870ecfbebf967d53858c7ae1eb39ece3689e
5
5
  SHA512:
6
- metadata.gz: b4794d89263b06204d56d09a89ac110f5382e1f1d3fdc914e2fc9a2534c8b16dcec608205643f4fcd9c69ef9ec918610e3d803e3eef6663be3356dfd70b1baba
7
- data.tar.gz: d4d2cd4b56df9f690e074695b07bda3dd7f5eb0499bf4060c8854225fb233389f75938d65a1a3134c83255988d660c288bdc8785001a2cc79c598b4397a54b00
6
+ metadata.gz: 8511b40e771e3c6c7f46d073fbae7d71f6c2df95947bc6d4c5bf6be11c6e7c6858b39a9a5d603691ee2cfa769329fde3416787ed1567bc27cd78dc1532408465
7
+ data.tar.gz: 1df40a88ab89a6e6ee83dd250a6965054069b1e958e7c0919a12b794273b3379e72c13ee585c980c2e4dd06f467cd459f66747440f656d529c5c971e8625f055
data/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
- ## [0.0.14] - 2025-03-29
1
+ ## [0.0.16] - 2025-03-31
2
2
 
3
- - Send credentials: 'include'
3
+ - Add a bunch of special-case support for Foobara::Auth domain convenience
4
+
5
+ ## [0.0.15] - 2025-03-30
6
+
7
+ - Better error handling in RemoteCommand
8
+
9
+ ## [0.0.14] - 2025-03-30
10
+
11
+ - Implement Auth domain support in RemoteCommand
4
12
 
5
13
  ## [0.0.13] - 2025-03-17
6
14
 
@@ -0,0 +1,13 @@
1
+ module Foobara
2
+ module RemoteGenerator
3
+ class Services
4
+ module Auth
5
+ class AccessTokensGenerator < TypescriptFromManifestBaseGenerator
6
+ def template_path
7
+ "Foobara/Auth/utils/accessTokens.ts.erb"
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module Foobara
2
+ module RemoteGenerator
3
+ class Services
4
+ module Auth
5
+ class LoginCommandGenerator < TypescriptFromManifestBaseGenerator
6
+ def template_path
7
+ "Foobara/Auth/LoginCommand.ts.erb"
8
+ end
9
+
10
+ def dependencies
11
+ super + [AccessTokensGenerator.new(relevant_manifest)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Foobara
2
+ module RemoteGenerator
3
+ class Services
4
+ module Auth
5
+ class LoginGenerator < CommandGenerator
6
+ def base_class_path
7
+ "Foobara/Auth/LoginCommand"
8
+ end
9
+
10
+ def dependencies
11
+ super + [LoginCommandGenerator.new(relevant_manifest)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Foobara
2
+ module RemoteGenerator
3
+ class Services
4
+ module Auth
5
+ class LogoutCommandGenerator < TypescriptFromManifestBaseGenerator
6
+ def template_path
7
+ "Foobara/Auth/LogoutCommand.ts.erb"
8
+ end
9
+
10
+ def dependencies
11
+ super + [AccessTokensGenerator.new(relevant_manifest)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Foobara
2
+ module RemoteGenerator
3
+ class Services
4
+ module Auth
5
+ class LogoutGenerator < CommandGenerator
6
+ def base_class_path
7
+ "Foobara/Auth/LogoutCommand"
8
+ end
9
+
10
+ def dependencies
11
+ super + [LogoutCommandGenerator.new(relevant_manifest)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Foobara
2
+ module RemoteGenerator
3
+ class Services
4
+ module Auth
5
+ class RefreshLoginGenerator < CommandGenerator
6
+ def base_class_path
7
+ "Foobara/Auth/LoginCommand"
8
+ end
9
+
10
+ def dependencies
11
+ super + [LoginCommandGenerator.new(relevant_manifest)]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "../typescript_from_manifest_base_generator"
2
+
3
+ module Foobara
4
+ module RemoteGenerator
5
+ class Services
6
+ module Auth
7
+ class RequiresAuthCommandGenerator < TypescriptFromManifestBaseGenerator
8
+ def template_path
9
+ "Foobara/Auth/RequiresAuthCommand.ts.erb"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "../command_generator"
2
+
3
+ module Foobara
4
+ module RemoteGenerator
5
+ class Services
6
+ module Auth
7
+ class RequiresAuthGenerator < CommandGenerator
8
+ def base_class_path
9
+ "Foobara/Auth/RequiresAuthCommand"
10
+ end
11
+
12
+ def dependencies
13
+ super + [RequiresAuthCommandGenerator.new(relevant_manifest)]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -35,6 +35,14 @@ module Foobara
35
35
  def command_errors_index_generator
36
36
  Services::CommandErrorsIndexGenerator.new(command_manifest)
37
37
  end
38
+
39
+ def base_class_path
40
+ "base/RemoteCommand"
41
+ end
42
+
43
+ def base_class_name
44
+ base_class_path.split("/").last
45
+ end
38
46
  end
39
47
  end
40
48
  end
@@ -24,7 +24,7 @@ module Foobara
24
24
 
25
25
  def command_generators
26
26
  @command_generators ||= domain_manifest.commands.map do |command_manifest|
27
- CommandGenerator.new(command_manifest)
27
+ generator_for(command_manifest)
28
28
  end
29
29
  end
30
30
 
@@ -28,8 +28,23 @@ module Foobara
28
28
  def manifest_to_generator_classes(manifest)
29
29
  case manifest
30
30
  when Manifest::Command
31
+ generator_classes = case manifest.full_command_name
32
+ when "Foobara::Auth::RefreshLogin"
33
+ Services::Auth::RefreshLoginGenerator
34
+ when "Foobara::Auth::Login"
35
+ Services::Auth::LoginGenerator
36
+ when "Foobara::Auth::Logout"
37
+ Services::Auth::LogoutGenerator
38
+ else
39
+ if manifest.requires_authentication?
40
+ Services::Auth::RequiresAuthGenerator
41
+ else
42
+ Services::CommandGenerator
43
+ end
44
+ end
45
+
31
46
  [
32
- Services::CommandGenerator,
47
+ *generator_classes,
33
48
  Services::CommandInputsGenerator,
34
49
  Services::CommandResultGenerator,
35
50
  Services::CommandErrorsGenerator,
@@ -190,10 +205,12 @@ module Foobara
190
205
  type_string = if type_declaration.is_a?(Manifest::Attributes)
191
206
  ts_type = attributes_to_ts_type(type_declaration, association_depth:, dependency_group:)
192
207
 
208
+ is_empty = type_declaration.attribute_declarations.empty?
209
+
193
210
  if name
194
- return "interface #{name} #{ts_type}"
211
+ return is_empty ? "undefined" : "interface #{name} #{ts_type}"
195
212
  else
196
- ts_type
213
+ is_empty ? "undefined" : ts_type
197
214
  end
198
215
  elsif type_declaration.is_a?(Manifest::Array)
199
216
  # TODO: which association_depth do we pass here?
@@ -1,10 +1,10 @@
1
- import RemoteCommand from "<%= path_to_root %>base/RemoteCommand"
1
+ import <%= base_class_name %> from "<%= path_to_root %><%= base_class_path %>"
2
2
 
3
3
  import Inputs from "./Inputs"
4
4
  import Result from "./Result"
5
5
  import { Error } from "./Errors"
6
6
 
7
- export class <%= command_name %> extends RemoteCommand<Inputs, Result, Error> {
7
+ export class <%= command_name %> extends <%= base_class_name %><Inputs, Result, Error> {
8
8
  static readonly organizationName = "<%= organization_name %>"
9
9
  static readonly domainName = "<%= domain_name %>"
10
10
  static readonly commandName = "<%= command_name %>"
@@ -0,0 +1,19 @@
1
+ import RemoteCommand from '../../base/RemoteCommand'
2
+ import { type FoobaraError } from '../../base/Error'
3
+ import { type Outcome } from '../../base/Outcome'
4
+ import { handleLogin } from './utils/accessTokens'
5
+
6
+ export default class LoginCommand<Inputs, Result, Error extends FoobaraError<any>>
7
+ extends RemoteCommand<Inputs, Result, Error> {
8
+ async _handleResponse (response: Response): Promise<Outcome<Result, Error>> {
9
+ if (response.ok) {
10
+ const accessToken: string | null = response.headers.get('X-Access-Token')
11
+
12
+ if (accessToken != null) {
13
+ handleLogin(this.urlBase, accessToken)
14
+ }
15
+ }
16
+
17
+ return await super._handleResponse(response)
18
+ }
19
+ }
@@ -0,0 +1,18 @@
1
+ import RemoteCommand from '../../base/RemoteCommand'
2
+ import { type FoobaraError } from '../../base/Error'
3
+ import { type Outcome } from '../../base/Outcome'
4
+ import { handleLogout } from './utils/accessTokens'
5
+
6
+ export default class LogoutCommand<Inputs, Result, Error extends FoobaraError<any>>
7
+ extends RemoteCommand<Inputs, Result, Error> {
8
+ async run (): Promise<Outcome<Result, Error>> {
9
+ const outcome = await super.run()
10
+
11
+ if (outcome.isSuccess()) {
12
+ // Broadcast logout event to all tabs
13
+ handleLogout(this.urlBase)
14
+ }
15
+
16
+ return outcome
17
+ }
18
+ }
@@ -0,0 +1,43 @@
1
+ import RemoteCommand from '../../base/RemoteCommand'
2
+ import { type Outcome } from '../../base/Outcome'
3
+ import { type FoobaraError } from '../../base/Error'
4
+ import { tokenForUrl } from './utils/accessTokens'
5
+
6
+ export default class RequiresAuthCommand<Inputs, Result, Error extends FoobaraError<any>>
7
+ extends RemoteCommand<Inputs, Result, Error> {
8
+ _buildRequestParams (): RequestInit {
9
+ const requestParams = super._buildRequestParams()
10
+
11
+ const bearerToken = tokenForUrl(this.urlBase)
12
+
13
+ if (bearerToken != null && requestParams.headers != null) {
14
+ requestParams.headers = { ...requestParams.headers, Authorization: `Bearer ${bearerToken}` }
15
+ }
16
+
17
+ return requestParams
18
+ }
19
+
20
+ async _handleResponse (response: Response): Promise<Outcome<Result, Error>> {
21
+ response = await this._handleUnauthenticated(response)
22
+
23
+ return await super._handleResponse(response)
24
+ }
25
+
26
+ async _handleUnauthenticated (response: Response): Promise<Response> {
27
+ if (response.status === 401) {
28
+ this.commandState = 'refreshing_authentication'
29
+
30
+ const { RefreshLogin } = await import('./RefreshLogin')
31
+ // See if we can authenticate using the refresh token
32
+ const refreshCommand = new RefreshLogin()
33
+ const outcome = await refreshCommand.run()
34
+
35
+ if (outcome.isSuccess()) {
36
+ this.commandState = 'executing'
37
+ response = await this._issueRequest()
38
+ }
39
+ }
40
+
41
+ return response
42
+ }
43
+ }
@@ -0,0 +1,28 @@
1
+ const accessTokens = new Map<string, string>()
2
+
3
+ const logout = (urlBase: string): void => { accessTokens.delete(urlBase) }
4
+ let handleLogout: (baseUrl: string) => void = logout
5
+
6
+ const tokenForUrl = (baseUrl: string): string | undefined => accessTokens.get(baseUrl)
7
+ const handleLogin: (baseUrl: string, accessToken: string) => void = (baseUrl, accessToken) => {
8
+ accessTokens.set(baseUrl, accessToken)
9
+ }
10
+
11
+ if (typeof BroadcastChannel !== 'undefined') {
12
+ const logoutChannel = new BroadcastChannel('foobara-auth-events')
13
+
14
+ logoutChannel.addEventListener('message', (event: MessageEvent<string>) => {
15
+ accessTokens.delete(event.data)
16
+ })
17
+
18
+ handleLogout = (baseUrl: string) => {
19
+ logout(baseUrl)
20
+ logoutChannel.postMessage(baseUrl)
21
+ }
22
+ }
23
+
24
+ export {
25
+ handleLogin,
26
+ handleLogout,
27
+ tokenForUrl
28
+ }
@@ -1,16 +1,11 @@
1
1
  import { type Outcome, SuccessfulOutcome, ErrorOutcome } from './Outcome'
2
2
  import { type FoobaraError } from './Error'
3
3
 
4
- export type commandState = 'initialized' | 'executing' | 'refreshing_auth' | 'succeeded' | 'errored' | 'failed'
5
-
6
- const accessTokens: Record<string, string> = {}
7
-
8
4
  export default abstract class RemoteCommand<Inputs, Result, CommandError extends FoobaraError> {
9
5
  static _urlBase: string | undefined
10
6
  static commandName: string
11
7
  static organizationName: string
12
8
  static domainName: string
13
- static authRequired: boolean = true // TODO: set this from generator
14
9
 
15
10
  // TODO: make use of domain's config instead of process.env directly.
16
11
  static get urlBase (): string {
@@ -39,11 +34,11 @@ export default abstract class RemoteCommand<Inputs, Result, CommandError extends
39
34
  return (this.constructor as typeof RemoteCommand<Inputs, Result, CommandError>).domainName
40
35
  }
41
36
 
42
- inputs: Inputs
37
+ inputs: Inputs | undefined
43
38
  outcome: null | Outcome<Result, CommandError>
44
- commandState: commandState
39
+ commandState: string
45
40
 
46
- constructor (inputs: Inputs) {
41
+ constructor (inputs: Inputs | undefined = undefined) {
47
42
  this.inputs = inputs
48
43
  this.commandState = 'initialized'
49
44
  this.outcome = null
@@ -77,68 +72,48 @@ export default abstract class RemoteCommand<Inputs, Result, CommandError extends
77
72
  }
78
73
 
79
74
  get commandPath (): string {
80
- return this.fullCommandName.replace('::', '/')
75
+ return this.fullCommandName.replaceAll('::', '/')
81
76
  }
82
77
 
83
78
  async run (): Promise<Outcome<Result, CommandError>> {
84
- const url = `${this.urlBase}/run/${this.commandPath}`
79
+ this.commandState = 'executing'
80
+ const response = await this._issueRequest()
85
81
 
86
- const bearerToken = accessTokens[this.urlBase]
82
+ this.outcome = await this._handleResponse(response)
83
+
84
+ return this.outcome
85
+ }
87
86
 
88
- const requestParams: RequestInit = {
87
+ _buildUrl (): string {
88
+ return `${this.urlBase}/run/${this.commandPath}`
89
+ }
90
+
91
+ _buildRequestParams (): RequestInit {
92
+ return {
89
93
  method: 'POST',
94
+ headers: { 'Content-Type': 'application/json' },
90
95
  body: JSON.stringify(this.inputs),
91
96
  credentials: 'include'
92
97
  }
98
+ }
93
99
 
94
- requestParams.headers = { 'Content-Type': 'application/json' }
95
-
96
- if (bearerToken != null) {
97
- requestParams.headers.Authorization = `Bearer ${bearerToken}`
98
- }
99
-
100
- this.commandState = 'executing'
101
- let response = await fetch(url, requestParams)
102
-
103
- if ((this.constructor as typeof RemoteCommand<Inputs, Result, CommandError>).authRequired &&
104
- response.status === 401) {
105
- this.commandState = 'refreshing_auth'
106
-
107
- // TODO: in generator make this conditional
108
- const { RefreshLogin } = await import('../Foobara/Auth')
109
- // See if we can authenticate using the refresh token
110
- const refreshCommand = new RefreshLogin({})
111
- const outcome = await refreshCommand.run()
112
-
113
- if (outcome.isSuccess()) {
114
- const bearerToken = accessTokens[this.urlBase]
115
-
116
- if (bearerToken != null) {
117
- requestParams.headers.Authorization = `Bearer ${bearerToken}`
118
- }
119
-
120
- this.commandState = 'executing'
121
- response = await fetch(url, requestParams)
122
- }
123
- }
100
+ async _issueRequest (): Promise<Response> {
101
+ return await fetch(this._buildUrl(), this._buildRequestParams())
102
+ }
124
103
 
125
- const body = await response.json()
104
+ async _handleResponse (response: Response): Promise<Outcome<Result, CommandError>> {
105
+ const text = await response.text()
106
+ const body = JSON.parse(text)
126
107
 
127
108
  if (response.ok) {
128
- const accessToken: string | null = response.headers.get('X-Access-Token')
129
-
130
- if (accessToken != null) {
131
- accessTokens[this.urlBase] = accessToken
132
- }
133
-
134
109
  this.commandState = 'succeeded'
135
110
  this.outcome = new SuccessfulOutcome<Result, CommandError>(body)
136
- } else if (response.status === 422) {
111
+ } else if (response.status === 422 || response.status === 401 || response.status === 403) {
137
112
  this.commandState = 'errored'
138
113
  this.outcome = new ErrorOutcome<Result, CommandError>(body)
139
114
  } else {
140
115
  this.commandState = 'failed'
141
- throw new Error(`not sure how to handle ${await response.text()}`)
116
+ throw new Error(`not sure how to handle ${text}`)
142
117
  }
143
118
 
144
119
  return this.outcome
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara-typescript-remote-command-generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -56,6 +56,14 @@ files:
56
56
  - src/remote_generator/services/aggregate_model_generator.rb
57
57
  - src/remote_generator/services/atom_entity_generator.rb
58
58
  - src/remote_generator/services/atom_model_generator.rb
59
+ - src/remote_generator/services/auth/access_tokens_generator.rb
60
+ - src/remote_generator/services/auth/login_command_generator.rb
61
+ - src/remote_generator/services/auth/login_generator.rb
62
+ - src/remote_generator/services/auth/logout_command_generator.rb
63
+ - src/remote_generator/services/auth/logout_generator.rb
64
+ - src/remote_generator/services/auth/refresh_login_generator.rb
65
+ - src/remote_generator/services/auth/requires_auth_command_generator.rb
66
+ - src/remote_generator/services/auth/requires_auth_generator.rb
59
67
  - src/remote_generator/services/command_errors_generator.rb
60
68
  - src/remote_generator/services/command_errors_index_generator.rb
61
69
  - src/remote_generator/services/command_generator.rb
@@ -98,6 +106,10 @@ files:
98
106
  - templates/Entity/Unloaded.ts.erb
99
107
  - templates/EntityVariants.ts.erb
100
108
  - templates/Error.ts.erb
109
+ - templates/Foobara/Auth/LoginCommand.ts.erb
110
+ - templates/Foobara/Auth/LogoutCommand.ts.erb
111
+ - templates/Foobara/Auth/RequiresAuthCommand.ts.erb
112
+ - templates/Foobara/Auth/utils/accessTokens.ts.erb
101
113
  - templates/Model/Aggregate.ts.erb
102
114
  - templates/Model/Atom.ts.erb
103
115
  - templates/Model/Model.ts.erb