Skip to content

Commit fbe8986

Browse files
committed
feat: add support for China
1 parent bdde29c commit fbe8986

File tree

9 files changed

+60
-30
lines changed

9 files changed

+60
-30
lines changed

.changeset/moody-bars-act.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smartthings/core-sdk": minor
3+
---
4+
5+
add support for China

src/endpoint-client.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@ export type HttpClientMethod =
2121

2222
export interface SmartThingsURLProvider {
2323
baseURL: string
24-
authURL: string
25-
keyApiURL: string
24+
authURL?: string
25+
keyApiURL?: string
2626
}
2727

28-
export const defaultSmartThingsURLProvider: SmartThingsURLProvider = {
28+
export const globalSmartThingsURLProvider: Required<SmartThingsURLProvider> = {
2929
baseURL: 'https://api.smartthings.com',
3030
authURL: 'https://auth-global.api.smartthings.com/oauth/token',
3131
keyApiURL: 'https://key.smartthings.com',
3232
}
3333

34+
export const chinaSmartThingsURLProvider: SmartThingsURLProvider = {
35+
// When login auth flow is added for China, make authURL required again.
36+
baseURL: 'https://api.samsungiotcloud.cn',
37+
}
38+
3439
export interface EndpointClientConfig {
3540
authenticator: Authenticator
3641
urlProvider?: SmartThingsURLProvider
@@ -50,8 +55,8 @@ export interface EndpointClientConfig {
5055
warningLogger?: (warnings: WarningFromHeader[] | string) => void | Promise<void>
5156
}
5257

53-
export interface ItemsList {
54-
items: []
58+
export interface ItemsList<T> {
59+
items: T[]
5560
_links?: {
5661
next?: {
5762
href: string
@@ -269,11 +274,11 @@ export class EndpointClient {
269274
return this.request('delete', path, undefined, params, options)
270275
}
271276

272-
public async getPagedItems<T = unknown>(path?: string, params?: HttpClientParams, options?: EndpointClientRequestOptions<ItemsList>): Promise<T[]> {
273-
let list = await this.get<ItemsList>(path, params, options)
277+
public async getPagedItems<T = unknown>(path?: string, params?: HttpClientParams, options?: EndpointClientRequestOptions<ItemsList<T>>): Promise<T[]> {
278+
let list = await this.get<ItemsList<T>>(path, params, options)
274279
const result = list.items
275280
while (list._links && list._links.next) {
276-
list = await this.get<ItemsList>(list._links.next.href, undefined, options)
281+
list = await this.get<ItemsList<T>>(list._links.next.href, undefined, options)
277282
result.push(...list.items)
278283
}
279284
return result

src/endpoint/devices.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ export interface CapabilityStatus {
783783
}
784784

785785
export interface ComponentStatus {
786-
[attributeName: string]: CapabilityStatus
786+
[capabilityName: string]: CapabilityStatus
787787
}
788788

789789
export interface DeviceStatus {

src/endpoint/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ export class SchemaEndpoint extends Endpoint {
302302
*/
303303
public async update(id: string, data: SchemaAppRequest, organizationId?: string): Promise<Status> {
304304
const options = organizationId ? { headerOverrides: { 'X-ST-Organization': organizationId } } : undefined
305+
// The API documentation says this should return the updated schema app but it does not.
306+
// Rather, on success, it simply returns an empty body.
305307
await this.client.put<SchemaApp>(`apps/${id}`, data, undefined, options)
306308
return SuccessStatusValue
307309
}

src/rest-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Authenticator } from './authenticator'
22
import { EndpointClientConfig, HttpClientHeaders, SmartThingsURLProvider,
3-
defaultSmartThingsURLProvider, WarningFromHeader } from './endpoint-client'
3+
globalSmartThingsURLProvider, WarningFromHeader } from './endpoint-client'
44
import { Logger } from './logger'
55

66

@@ -30,7 +30,7 @@ export class RESTClient {
3030
constructor(authenticator: Authenticator, config?: RESTClientConfig) {
3131
const defaultConfig = {
3232
authenticator,
33-
urlProvider: defaultSmartThingsURLProvider,
33+
urlProvider: globalSmartThingsURLProvider,
3434
useAuth: true,
3535
}
3636
const headers = (config && config.headers)

src/signature.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import axios from 'axios'
2-
import sshpk from 'sshpk'
32
import httpSignature from 'http-signature'
4-
import { SmartThingsURLProvider, defaultSmartThingsURLProvider } from './endpoint-client'
3+
import sshpk from 'sshpk'
4+
5+
import { SmartThingsURLProvider, globalSmartThingsURLProvider } from './endpoint-client'
56
import { Logger } from './logger'
67

78

@@ -23,11 +24,14 @@ export class HttpKeyResolver {
2324
private keyCacheTTL: number
2425

2526
constructor(config?: KeyResolverConfig ) {
26-
this.keyApiUrl = defaultSmartThingsURLProvider.keyApiURL
27+
this.keyApiUrl = globalSmartThingsURLProvider.keyApiURL
2728
this.keyCache = undefined
2829
this.keyCacheTTL = (24 * 60 * 60 * 1000)
2930
if (config) {
3031
if (config.urlProvider) {
32+
if (!config.urlProvider.keyApiURL) {
33+
throw Error('keyApiURL is required in urlProvider')
34+
}
3135
this.keyApiUrl = config.urlProvider.keyApiURL
3236
}
3337
if (config.keyCacheTTL) {

src/st-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { SchedulesEndpoint } from './endpoint/schedules'
2626
import { SchemaEndpoint } from './endpoint/schema'
2727
import { ServicesEndpoint } from './endpoint/services'
2828
import { VirtualDevicesEndpoint } from './endpoint/virtualdevices'
29-
import { SmartThingsURLProvider, defaultSmartThingsURLProvider, HttpClientHeaders } from './endpoint-client'
29+
import { SmartThingsURLProvider, globalSmartThingsURLProvider, HttpClientHeaders } from './endpoint-client'
3030

3131

3232
export class SmartThingsClient extends RESTClient {
@@ -103,7 +103,7 @@ export class SmartThingsOAuthClient {
103103

104104
constructor(private clientId: string, private clientSecret: string,
105105
private redirectUri: string, urlProvider?: SmartThingsURLProvider) {
106-
this.authURL = urlProvider?.authURL || defaultSmartThingsURLProvider.authURL
106+
this.authURL = urlProvider?.authURL || globalSmartThingsURLProvider.authURL
107107
}
108108

109109
// eslint-disable-next-line @typescript-eslint/no-explicit-any

test/unit/authenticator.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import axios from '../../__mocks__/axios'
2-
import { MutexInterface } from 'async-mutex'
2+
import { type MutexInterface } from 'async-mutex'
33

44
import {
5-
AuthData,
6-
RefreshData,
5+
type AuthData,
6+
type RefreshData,
77
NoOpAuthenticator,
88
BearerTokenAuthenticator,
99
RefreshTokenAuthenticator,
1010
RefreshTokenStore,
1111
SequentialRefreshTokenAuthenticator,
1212
} from '../../src/authenticator'
13-
import { defaultSmartThingsURLProvider } from '../../src/endpoint-client'
13+
import { globalSmartThingsURLProvider } from '../../src/endpoint-client'
1414

1515

1616
class TokenStore implements RefreshTokenStore {
@@ -23,6 +23,7 @@ class TokenStore implements RefreshTokenStore {
2323
return Promise.resolve()
2424
}
2525
}
26+
2627
describe('authenticators', () => {
2728
afterEach(() => {
2829
jest.clearAllMocks()
@@ -59,7 +60,7 @@ describe('authenticators', () => {
5960

6061
const tokenStore = new TokenStore()
6162
const authenticator = new RefreshTokenAuthenticator('a-refreshable-bearer-token', tokenStore)
62-
const endpointConfig = { urlProvider: defaultSmartThingsURLProvider, authenticator }
63+
const endpointConfig = { urlProvider: globalSmartThingsURLProvider, authenticator }
6364

6465
expect(await authenticator.refresh(endpointConfig)).toStrictEqual({ Authorization: 'Bearer the-access-token' })
6566

@@ -86,7 +87,7 @@ describe('authenticators', () => {
8687

8788
const tokenStore = new TokenStore()
8889
const authenticator = new RefreshTokenAuthenticator('a-refreshable-bearer-token', tokenStore)
89-
const endpointConfig = { urlProvider: defaultSmartThingsURLProvider, authenticator }
90+
const endpointConfig = { urlProvider: globalSmartThingsURLProvider, authenticator }
9091
let message
9192
try {
9293
await authenticator.refresh(endpointConfig)
@@ -120,7 +121,7 @@ describe('authenticators', () => {
120121
},
121122
})
122123

123-
const endpointConfig = { urlProvider: defaultSmartThingsURLProvider, authenticator }
124+
const endpointConfig = { urlProvider: globalSmartThingsURLProvider, authenticator }
124125

125126
it('updates token', async () => {
126127
await authenticator.refresh(endpointConfig)

test/unit/endpoint-client.test.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
import { Mutex } from 'async-mutex'
2-
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, ParamsSerializerOptions } from 'axios'
2+
import axios, { AxiosError, type AxiosRequestConfig, type AxiosResponse, type ParamsSerializerOptions } from 'axios'
33
import qs from 'qs'
44

5-
import { AuthData, Authenticator, BearerTokenAuthenticator, NoOpAuthenticator, RefreshData,
6-
RefreshTokenAuthenticator, RefreshTokenStore, SequentialRefreshTokenAuthenticator }
7-
from '../../src/authenticator'
8-
import { defaultSmartThingsURLProvider, EndpointClient, EndpointClientConfig, HttpClientHeaders, parseWarningHeader } from '../../src/endpoint-client'
9-
import { Logger, NoLogLogger } from '../../src/logger'
5+
import {
6+
type AuthData,
7+
type Authenticator,
8+
BearerTokenAuthenticator,
9+
NoOpAuthenticator,
10+
type RefreshData,
11+
RefreshTokenAuthenticator,
12+
type RefreshTokenStore,
13+
SequentialRefreshTokenAuthenticator,
14+
} from '../../src/authenticator'
15+
import {
16+
EndpointClient,
17+
type EndpointClientConfig,
18+
type HttpClientHeaders,
19+
globalSmartThingsURLProvider,
20+
parseWarningHeader,
21+
} from '../../src/endpoint-client'
22+
import { type Logger, NoLogLogger } from '../../src/logger'
1023

1124

1225
jest.mock('axios')
@@ -86,7 +99,7 @@ describe('EndpointClient', () => {
8699
let client: EndpointClient
87100

88101
const configWithoutHeaders = {
89-
urlProvider: defaultSmartThingsURLProvider,
102+
urlProvider: globalSmartThingsURLProvider,
90103
authenticator: new RefreshTokenAuthenticator(token, tokenStore),
91104
baseURL: 'https://api.smartthings.com',
92105
authURL: 'https://auth.smartthings.com',

0 commit comments

Comments
 (0)