@@ -48,6 +48,7 @@ export interface GenerateSuggestionsResponse {
4848
4949import CodeWhispererSigv4Client = require( '../client/sigv4/codewhisperersigv4client' )
5050import CodeWhispererTokenClient = require( '../client/token/codewhispererbearertokenclient' )
51+ import { getErrorId } from './utils'
5152
5253// Right now the only difference between the token client and the IAM client for codewhsiperer is the difference in function name
5354// This abstract class can grow in the future to account for any additional changes across the clients
@@ -156,8 +157,8 @@ export class CodeWhispererServiceIAM extends CodeWhispererServiceBase {
156157 */
157158export class CodeWhispererServiceToken extends CodeWhispererServiceBase {
158159 client : CodeWhispererTokenClient
159- /** Debounce getSubscriptionStatus by storing the current, pending promise (if any). */
160- #getSubscriptionStatusPromise: ReturnType < typeof this . createSubscriptionToken > | undefined
160+ /** Debounce createSubscriptionToken by storing the current, pending promise (if any). */
161+ #createSubscriptionTokenPromise: Promise < CodeWhispererTokenClient . CreateSubscriptionTokenResponse > | undefined
161162
162163 constructor (
163164 private credentialsProvider : CredentialsProvider ,
@@ -368,34 +369,70 @@ export class CodeWhispererServiceToken extends CodeWhispererServiceBase {
368369 }
369370
370371 /**
372+ * (debounced by default)
373+ *
371374 * cool api you have there 🥹
372375 */
373376 async createSubscriptionToken ( request : CodeWhispererTokenClient . CreateSubscriptionTokenRequest ) {
374- return this . client . createSubscriptionToken ( this . withProfileArn ( request ) ) . promise ( )
375- }
376-
377- /**
378- * Gets the Subscription status of the given user.
379- */
380- async getSubscriptionStatus ( ) : ReturnType < typeof this . createSubscriptionToken > {
381377 // Debounce.
382- if ( this . #getSubscriptionStatusPromise ) {
383- // this.logging.debug('getSubscriptionStatus : debounced')
384- return this . #getSubscriptionStatusPromise
378+ if ( this . #createSubscriptionTokenPromise ) {
379+ // this.logging.debug('createSubscriptionTokenPromise : debounced')
380+ return this . #createSubscriptionTokenPromise
385381 }
386382
387- this . #getSubscriptionStatusPromise = ( async ( ) => {
383+ this . #createSubscriptionTokenPromise = ( async ( ) => {
388384 try {
389- const resp = await this . createSubscriptionToken ( {
390- // clientToken: this.credentialsProvider.getCredentials('bearer').token,
391- } )
392- return resp
385+ return this . client . createSubscriptionToken ( this . withProfileArn ( request ) ) . promise ( )
393386 } finally {
394- this . #getSubscriptionStatusPromise = undefined
387+ this . #createSubscriptionTokenPromise = undefined
395388 }
396389 } ) ( )
397390
398- return this . #getSubscriptionStatusPromise
391+ return this . #createSubscriptionTokenPromise
392+ }
393+
394+ /**
395+ * Gets the Subscription status of the given user.
396+ *
397+ * @param statusOnly use this if you don't need the encodedVerificationUrl, else a ConflictException is treated as "ACTIVE"
398+ */
399+ async getSubscriptionStatus (
400+ statusOnly ?: boolean
401+ ) : Promise < { status : 'active' | 'active-expiring' | 'none' ; encodedVerificationUrl ?: string } > {
402+ // NOTE: The subscription API behaves in a non-intuitive way.
403+ // https://github.com/aws/amazon-q-developer-cli-autocomplete/blob/86edd86a338b549b5192de67c9fdef240e6014b7/crates/chat-cli/src/cli/chat/mod.rs#L4079-L4102
404+ //
405+ // If statusOnly=true, the service only returns "ACTIVE" and "INACTIVE".
406+ // If statusOnly=false, the following spec applies:
407+ //
408+ // 1. "ACTIVE" => 'active-expiring':
409+ // - Active but cancelled. User *has* a subscription, but set to *not auto-renew* (i.e., cancelled).
410+ // 2. "INACTIVE" => 'none':
411+ // - User has no subscription at all (no Pro access).
412+ // 3. ConflictException => 'active':
413+ // - User has an active subscription *with auto-renewal enabled*.
414+ //
415+ // Also, it is currently not possible to subscribe or re-subscribe via console, only IDE/CLI.
416+ try {
417+ const r = await this . createSubscriptionToken ( {
418+ statusOnly : ! ! statusOnly ,
419+ // clientToken: this.credentialsProvider.getCredentials('bearer').token,
420+ } )
421+ const status = r . status === 'ACTIVE' ? 'active-expiring' : 'none'
422+
423+ return {
424+ status : status ,
425+ encodedVerificationUrl : r . encodedVerificationUrl ,
426+ }
427+ } catch ( e ) {
428+ if ( getErrorId ( e as Error ) === 'ConflictException' ) {
429+ return {
430+ status : 'active' ,
431+ }
432+ }
433+
434+ throw e
435+ }
399436 }
400437
401438 /**
@@ -409,9 +446,9 @@ export class CodeWhispererServiceToken extends CodeWhispererServiceBase {
409446 if ( cancelToken ?. isCancellationRequested ) {
410447 return false
411448 }
412- const s = await this . getSubscriptionStatus ( )
449+ const s = await this . getSubscriptionStatus ( true )
413450 this . logging . info ( `waitUntilSubscriptionActive: ${ s . status } ` )
414- if ( s . status === 'ACTIVE ') {
451+ if ( s . status !== 'none ') {
415452 return true
416453 }
417454 } ,
0 commit comments