swcombine.js 0.0.10 → 0.1.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.
- package/LICENSE +5 -17
- package/README.md +668 -97
- package/dist/index.d.ts +5 -510
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +23 -0
- package/dist/src/auth/index.d.ts +8 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/oauth-manager.d.ts +168 -0
- package/dist/src/auth/oauth-manager.d.ts.map +1 -0
- package/dist/src/auth/oauth.d.ts +101 -0
- package/dist/src/auth/oauth.d.ts.map +1 -0
- package/dist/src/auth/scopes.d.ts +61 -0
- package/dist/src/auth/scopes.d.ts.map +1 -0
- package/dist/src/auth/types.d.ts +118 -0
- package/dist/src/auth/types.d.ts.map +1 -0
- package/dist/src/client/base-resource.d.ts +33 -0
- package/dist/src/client/base-resource.d.ts.map +1 -0
- package/dist/src/client/client.d.ts +85 -0
- package/dist/src/client/client.d.ts.map +1 -0
- package/dist/src/client/errors.d.ts +63 -0
- package/dist/src/client/errors.d.ts.map +1 -0
- package/dist/src/client/http-client.d.ts +35 -0
- package/dist/src/client/http-client.d.ts.map +1 -0
- package/dist/src/client/index.d.ts +15 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/rate-limit.d.ts +12 -0
- package/dist/src/client/rate-limit.d.ts.map +1 -0
- package/dist/src/client/resources/api.d.ts +42 -0
- package/dist/src/client/resources/api.d.ts.map +1 -0
- package/dist/src/client/resources/character.d.ts +98 -0
- package/dist/src/client/resources/character.d.ts.map +1 -0
- package/dist/src/client/resources/faction.d.ts +70 -0
- package/dist/src/client/resources/faction.d.ts.map +1 -0
- package/dist/src/client/resources/index.d.ts +8 -0
- package/dist/src/client/resources/index.d.ts.map +1 -0
- package/dist/src/client/resources/inventory.d.ts +205 -0
- package/dist/src/client/resources/inventory.d.ts.map +1 -0
- package/dist/src/client/types.d.ts +78 -0
- package/dist/src/client/types.d.ts.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/timestamp.d.ts +188 -0
- package/dist/src/utils/timestamp.d.ts.map +1 -0
- package/dist/src/utils/types.d.ts +42 -0
- package/dist/src/utils/types.d.ts.map +1 -0
- package/package.json +35 -55
- package/dist/index.cjs.js +0 -704
- package/dist/index.esm.js +0 -704
package/README.md
CHANGED
|
@@ -1,98 +1,669 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
Typescript and Javascript SDK for the Star Wars Combine 2.0 Web Services.
|
|
4
|
-
The SDK is currently a WIP. Status is as follows:
|
|
5
|
-
|
|
6
|
-
## API SDK Status
|
|
7
|
-
|
|
8
|
-
- :x: = Not yet implemented
|
|
9
|
-
- :construction: = Partially implemented
|
|
10
|
-
- :heavy_check_mark: = Implemented
|
|
11
|
-
- :white_check_mark: = Implemented and covered by tests
|
|
12
|
-
|
|
13
|
-
| Endpoint | Status | Availability | OAuth scope required (if applicable) |
|
|
14
|
-
|--------------------------------------------|--------------------|------------------|------------------------------------------------------------------------------------------------------------|
|
|
15
|
-
| **<h3>OAuth</h3>** | :x: | Public | |
|
|
16
|
-
| Get token using server flow (node.js only) | :x: | Public | |
|
|
17
|
-
| Get token using client flow (browser only) | :x: | Public | |
|
|
18
|
-
| Get granted permissions | :x: | OAuth :lock: | |
|
|
19
|
-
| **<h3>Permissions</h3>** | :x: | | |
|
|
20
|
-
| List permissions | :heavy_check_mark: | Public | |
|
|
21
|
-
| **<h3>Rate Limits</h3>** | :x: | | |
|
|
22
|
-
| List rate limits | :x: | Public | |
|
|
23
|
-
| **<h3>Time</h3>** | :white_check_mark: | | |
|
|
24
|
-
| Get current SWC time | :white_check_mark: | Public/on device | |
|
|
25
|
-
| Convert between SWC and Unix time | :white_check_mark: | Public/on device | |
|
|
26
|
-
| Convert between SWC time and Date objects | :white_check_mark: | Public/on device | |
|
|
27
|
-
| **<h3>Character</h3>** | :x: | | |
|
|
28
|
-
| Get character info | :x: | OAuth :lock: | character_read |
|
|
29
|
-
| Get character's credits | :x: | OAuth :lock: | character_credits |
|
|
30
|
-
| Get character's creditlog | :x: | OAuth :lock: | character_credits |
|
|
31
|
-
| Get UID by handle(handlecheck) | :x: | Public | |
|
|
32
|
-
| List received messages | :x: | OAuth :lock: | messages_read |
|
|
33
|
-
| List sent messages | :x: | OAuth :lock: | messages_read |
|
|
34
|
-
| Send message from character | :x: | OAuth :lock: | messages_send |
|
|
35
|
-
| Get message by id | :x: | OAUth :lock: | messages_read |
|
|
36
|
-
| Delete message by id | :x: | OAuth :lock: | messages_delete |
|
|
37
|
-
| Get character's skills | :x: | OAuth :lock: | character_skills |
|
|
38
|
-
| List character's privileges | :x: | OAuth :lock: | character_privileges |
|
|
39
|
-
| Check if character has privilege | :x: | OAuth :lock: | character_privileges, and must be logged in as someone that can view the privileges of others. |
|
|
40
|
-
| Grant privilege to a character | :x: | OAuth :lock: | character_privileges, and must be logged in as someone that has the ability to grant privileges to others. |
|
|
41
|
-
| Revoke privilege from character | :x: | OAuth :lock: | character_privileges, and must be logged in as someone that can grant/revoke privileges of others. |
|
|
42
|
-
| **<h3>Datacard</h3>** | :x: | | |
|
|
43
|
-
| List datacards | :x: | OAuth :lock: | faction_datacards_read |
|
|
44
|
-
| Get datacard | :x: | OAuth :lock: | faction_datacards_read |
|
|
45
|
-
| Assign datacard | :x: | OAuth :lock: | faction_datacards_write |
|
|
46
|
-
| Delete datacard assignment | :x: | OAuth :lock: | faction_datacards_write |
|
|
47
|
-
| **<h3>Events</h3>** | :x: | | |
|
|
48
|
-
| List events for character | :x: | OAuth :lock: | character_events |
|
|
49
|
-
| Get event by uid | :x: | OAuth :lock: | character_events |
|
|
50
|
-
| **<h3>Factions</h3>** | :x: | | |
|
|
51
|
-
| List factions | :x: | Public | |
|
|
52
|
-
| Get faction by uid | :x: | Public | |
|
|
53
|
-
| Get faction credits | :x: | OAuth :lock: | faction_credits_read |
|
|
54
|
-
| Get faction's creditlog | :x: | OAuth :lock: | faction_credits_read |
|
|
55
|
-
| Send faction credits | :x: | OAuth :lock: | faction_credits_write |
|
|
56
|
-
| List faction's budgets | :x: | OAuth :lock: | faction_budgets_read |
|
|
57
|
-
| Get faction budget by uid | :x: | OAuth :lock: | faction_budgets_read |
|
|
58
|
-
| List faction members | :x: | OAuth :lock: | faction_members |
|
|
59
|
-
| Update faction member's infofields | :x: | OAuth :lock: | faction_members, and must be logged in as someone that can update infofields |
|
|
60
|
-
| List faction's stock holders | :x: | OAuth :lock: | faction_stocks |
|
|
61
|
-
| **<h3>Galaxy</h3>** | :x: | | |
|
|
62
|
-
| List sectors | :x: | Public | |
|
|
63
|
-
| Get sector by uid | :x: | Public | |
|
|
64
|
-
| List systems | :x: | Public | |
|
|
65
|
-
| Get system by uid | :x: | Public | |
|
|
66
|
-
| List planets | :x: | Public | |
|
|
67
|
-
| Get planet by uid | :x: | Public | |
|
|
68
|
-
| List stations | :x: | Public | |
|
|
69
|
-
| Get station by uid | :x: | Public | |
|
|
70
|
-
| List cities | :x: | Public | |
|
|
71
|
-
| Get city by uid | :x: | Public | |
|
|
72
|
-
| **<h3>Inventory</h3>** | :x: | | |
|
|
73
|
-
| List inventories | :x: | OAuth :lock: | [personal/faction]_inv_overview |
|
|
74
|
-
| List entities in inventory | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_read |
|
|
75
|
-
| Get one entity in inventory | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_read |
|
|
76
|
-
| Modify entity info | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_assign |
|
|
77
|
-
| Rename entity | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_rename |
|
|
78
|
-
| Makeover entity | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_makeover |
|
|
79
|
-
| Apply tag to entity | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_tags_write |
|
|
80
|
-
| Remove tag from entity | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_tags_write |
|
|
81
|
-
| Clear all tags from entity | :x: | OAuth :lock: | [personal/faction]\_inv_[inventory]_tags_write |
|
|
82
|
-
| **<h3>Location</h3>** | :x: | | |
|
|
83
|
-
| Get location for specified entity | :x: | OAuth :lock: | character_location |
|
|
84
|
-
| **<h3>Market</h3>** | :x: | |
|
|
85
|
-
| List all public market vendors | :x: | Public |
|
|
86
|
-
| Get vendor info | :x: | Public |
|
|
87
|
-
| **<h3>News</h3>** | :x: | | |
|
|
88
|
-
| List GNS news | :x: | Public | |
|
|
89
|
-
| Get one GNS Item | :x: | Public | |
|
|
90
|
-
| List Sim news | :x: | Public | |
|
|
91
|
-
| Get one Sim news item | :x: | Public | |
|
|
92
|
-
| **<h3>Types</h3>** | :x: | | |
|
|
93
|
-
| List classes of type | :x: | Public | |
|
|
94
|
-
| List entities of type | :x: | Public | |
|
|
95
|
-
| List entities by class and type | :x: | Public | |
|
|
96
|
-
| Get type for existing entity | :x: | Public | |
|
|
97
|
-
| List all entity types | :x: | Public | |
|
|
1
|
+
# swcombine.js
|
|
98
2
|
|
|
3
|
+
TypeScript SDK for the [Star Wars Combine API](https://www.swcombine.com/ws)
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
### ✅ Phase 1: OAuth 2.0 Authentication (Complete)
|
|
14
|
+
|
|
15
|
+
- **Three flexible authentication modes:**
|
|
16
|
+
- Full OAuth mode with automatic token refresh and storage
|
|
17
|
+
- Direct access token mode (for pre-obtained tokens)
|
|
18
|
+
- Utility OAuth mode (URL generation and manual token refresh without storage)
|
|
19
|
+
- Full OAuth 2.0 flow implementation
|
|
20
|
+
- Authorization URL generation
|
|
21
|
+
- Token exchange (authorization code → access token)
|
|
22
|
+
- Automatic token refresh (when using storage)
|
|
23
|
+
- Manual token refresh utility method
|
|
24
|
+
- Token revocation
|
|
25
|
+
- Custom token storage support
|
|
26
|
+
- Built-in memory storage
|
|
27
|
+
- **Strongly-typed OAuth scopes** (100+ scopes with descriptions)
|
|
28
|
+
|
|
29
|
+
### ✅ Phase 2: HTTP Client (Complete)
|
|
30
|
+
|
|
31
|
+
- Resource-based API client (`client.character.get()`)
|
|
32
|
+
- Automatic authentication with OAuth tokens
|
|
33
|
+
- Comprehensive error handling (typed error classes)
|
|
34
|
+
- Rate limit tracking and RateLimitError
|
|
35
|
+
- Support for 4 core resources: `api`, `character`, `faction`, `inventory`
|
|
36
|
+
- Inventory resource with 11 entity types (ships, vehicles, stations, cities, facilities, planets, items, npcs, droids, creatures, materials)
|
|
37
|
+
- Advanced filtering with InventoryFilters interface
|
|
38
|
+
- Entity-specific operations (properties and tags management)
|
|
39
|
+
- Custom request options (headers, timeout, abort signals)
|
|
40
|
+
|
|
41
|
+
### 🚧 Phase 3: API Resources (Coming Soon)
|
|
42
|
+
|
|
43
|
+
- Additional API resources (events, market, etc.)
|
|
44
|
+
- Type-safe response types from API spec
|
|
45
|
+
- Auto-generated resource methods
|
|
46
|
+
- Full TypeScript support for all endpoints
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
### Authentication
|
|
51
|
+
|
|
52
|
+
The SDK supports three authentication modes:
|
|
53
|
+
|
|
54
|
+
#### 1. Full OAuth Mode (Recommended for Server-Side)
|
|
55
|
+
|
|
56
|
+
Complete OAuth flow with automatic token management and refresh:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { OAuthManager, SWCClient, MemoryTokenStorage } from "swcombine.js";
|
|
60
|
+
|
|
61
|
+
// Initialize OAuth manager with storage for token persistence
|
|
62
|
+
const storage = new MemoryTokenStorage(); // Or implement custom TokenStorage
|
|
63
|
+
const oauth = new OAuthManager(
|
|
64
|
+
{
|
|
65
|
+
clientId: "your-client-id",
|
|
66
|
+
clientSecret: "your-client-secret",
|
|
67
|
+
redirectUri: "https://example.com/callback",
|
|
68
|
+
defaultScopes: ['character_read', 'faction_members'],
|
|
69
|
+
},
|
|
70
|
+
storage
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Step 1: Generate authorization URL
|
|
74
|
+
const authUrl = oauth.getAuthorizationUrl({
|
|
75
|
+
state: "random-csrf-token",
|
|
76
|
+
accessType: "offline", // Request refresh token
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Redirect user to authUrl...
|
|
80
|
+
|
|
81
|
+
// Step 2: Handle callback (after user authorizes)
|
|
82
|
+
const tokens = await oauth.handleCallback(authorizationCode);
|
|
83
|
+
|
|
84
|
+
// Step 3: Create API client (tokens auto-refresh)
|
|
85
|
+
const client = new SWCClient({ auth: oauth });
|
|
86
|
+
const character = await client.character.get();
|
|
87
|
+
|
|
88
|
+
// Step 4: Revoke when done
|
|
89
|
+
await oauth.revoke();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### 2. Direct Access Token Mode
|
|
93
|
+
|
|
94
|
+
Use when you already have an access token (no auto-refresh):
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { SWCClient } from "swcombine.js";
|
|
98
|
+
|
|
99
|
+
// Create client with direct access token
|
|
100
|
+
const client = new SWCClient({ auth: "your-access-token" });
|
|
101
|
+
|
|
102
|
+
// Make API calls (token won't auto-refresh)
|
|
103
|
+
const character = await client.character.get();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### 3. Utility OAuth Mode
|
|
107
|
+
|
|
108
|
+
Use OAuthManager as a utility without storage (for URL generation or manual token refresh):
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { OAuthManager } from "swcombine.js";
|
|
112
|
+
|
|
113
|
+
// Initialize without storage
|
|
114
|
+
const oauth = new OAuthManager({
|
|
115
|
+
clientId: "your-client-id",
|
|
116
|
+
clientSecret: "your-client-secret",
|
|
117
|
+
redirectUri: "https://example.com/callback",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Generate authorization URL
|
|
121
|
+
const authUrl = oauth.getAuthorizationUrl({
|
|
122
|
+
scopes: ['character_read'],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Manually refresh a token (without storage)
|
|
126
|
+
const newTokens = await oauth.refreshAccessToken(refreshToken);
|
|
127
|
+
// Use newTokens.access_token with direct access token mode
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Using the HTTP Client
|
|
131
|
+
|
|
132
|
+
Once authenticated, use the `SWCClient` to interact with the API:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { SWCClient } from "swcombine.js";
|
|
136
|
+
|
|
137
|
+
// Create client (with OAuth manager or direct token)
|
|
138
|
+
const client = new SWCClient({ auth: oauth }); // or { auth: "token" }
|
|
139
|
+
|
|
140
|
+
// Character endpoints
|
|
141
|
+
const character = await client.character.get();
|
|
142
|
+
const skills = await client.character.skills.get();
|
|
143
|
+
const credits = await client.character.credits.get();
|
|
144
|
+
const creditlog = await client.character.creditlog.get();
|
|
145
|
+
const messages = await client.character.messages.get();
|
|
146
|
+
const message = await client.character.messages.get("123"); // Specific message
|
|
147
|
+
|
|
148
|
+
// Transfer character credits
|
|
149
|
+
await client.character.credits.post({
|
|
150
|
+
recipient: "Character Name",
|
|
151
|
+
amount: 50000,
|
|
152
|
+
reason: "Payment for services", // optional
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Send a message
|
|
156
|
+
await client.character.messages.put({
|
|
157
|
+
receivers: "Character1;Character2;Character3",
|
|
158
|
+
communication: "Hello everyone!",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Delete a message
|
|
162
|
+
await client.character.messages.delete("message-uid");
|
|
163
|
+
|
|
164
|
+
// Grant or revoke a privilege
|
|
165
|
+
await client.character.privilege.post("privilegegroup/privilege", {
|
|
166
|
+
revoke: "1", // optional - set to revoke instead of grant
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Get character UID by handle
|
|
170
|
+
const characterInfo = await client.character.handlecheck.get("CharacterName");
|
|
171
|
+
|
|
172
|
+
// Faction endpoints
|
|
173
|
+
const myFaction = await client.faction.get();
|
|
174
|
+
const specificFaction = await client.faction.get("123");
|
|
175
|
+
const members = await client.faction.members.get();
|
|
176
|
+
const budgets = await client.faction.budgets.get();
|
|
177
|
+
const stockholders = await client.faction.stockholders.get();
|
|
178
|
+
|
|
179
|
+
// Transfer faction credits
|
|
180
|
+
await client.faction.credits.post({
|
|
181
|
+
recipient: "Character Name",
|
|
182
|
+
amount: 100000,
|
|
183
|
+
budget: "budget-uid", // optional
|
|
184
|
+
reason: "Payment for services", // optional
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Inventory endpoints
|
|
188
|
+
const inventory = await client.inventory.get("character-uid");
|
|
189
|
+
|
|
190
|
+
// List entity types (ships, vehicles, stations, etc.)
|
|
191
|
+
const ships = await client.inventory.ships.get("character-uid");
|
|
192
|
+
const ownedShips = await client.inventory.ships.get("character-uid", "owner");
|
|
193
|
+
|
|
194
|
+
// List with filters
|
|
195
|
+
const filteredShips = await client.inventory.ships.get("character-uid", "owner", {
|
|
196
|
+
filters: {
|
|
197
|
+
filter_type: ["tags"],
|
|
198
|
+
filter_value: { tags: ["combat", "transport"] },
|
|
199
|
+
filter_inclusion: { tags: "includes" },
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Get specific entity
|
|
204
|
+
const ship = await client.inventory.entity.ships.get("ship-uid");
|
|
205
|
+
|
|
206
|
+
// Manage entity properties
|
|
207
|
+
const shipProps = await client.inventory.entity.ships.properties.get("ship-uid");
|
|
208
|
+
await client.inventory.entity.ships.properties.post("ship-uid", {
|
|
209
|
+
property_name: "custom_name",
|
|
210
|
+
property_value: "My Ship",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Manage entity tags
|
|
214
|
+
const shipTags = await client.inventory.entity.ships.tags.get("ship-uid");
|
|
215
|
+
await client.inventory.entity.ships.tags.post("ship-uid", { tags: ["combat"] });
|
|
216
|
+
await client.inventory.entity.ships.tags.delete("ship-uid", { tags: ["old-tag"] });
|
|
217
|
+
|
|
218
|
+
// API utility endpoints
|
|
219
|
+
const rateLimits = await client.api.rateLimits();
|
|
220
|
+
const permissions = await client.api.permissions();
|
|
221
|
+
const serverTime = await client.api.time();
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Error Handling
|
|
225
|
+
|
|
226
|
+
The client throws typed errors for different failure scenarios:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import {
|
|
230
|
+
SWCClient,
|
|
231
|
+
RateLimitError,
|
|
232
|
+
AuthenticationError,
|
|
233
|
+
ValidationError,
|
|
234
|
+
NotFoundError,
|
|
235
|
+
ServerError,
|
|
236
|
+
NetworkError,
|
|
237
|
+
} from "swcombine.js";
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const character = await client.character.get();
|
|
241
|
+
console.log(character);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (error instanceof RateLimitError) {
|
|
244
|
+
// Rate limit exceeded
|
|
245
|
+
console.log(`Rate limited! Reset at ${error.rateLimit.reset}`);
|
|
246
|
+
console.log(`Remaining: ${error.rateLimit.remaining}/${error.rateLimit.limit}`);
|
|
247
|
+
} else if (error instanceof AuthenticationError) {
|
|
248
|
+
// 401 - Need to re-authenticate
|
|
249
|
+
console.log("Authentication failed");
|
|
250
|
+
} else if (error instanceof ValidationError) {
|
|
251
|
+
// 400 - Bad request
|
|
252
|
+
console.log("Invalid request:", error.response);
|
|
253
|
+
} else if (error instanceof NotFoundError) {
|
|
254
|
+
// 404 - Resource not found
|
|
255
|
+
console.log("Resource not found");
|
|
256
|
+
} else if (error instanceof ServerError) {
|
|
257
|
+
// 5xx - Server error
|
|
258
|
+
console.log(`Server error (${error.statusCode}):`, error.message);
|
|
259
|
+
} else if (error instanceof NetworkError) {
|
|
260
|
+
// Network/fetch failure
|
|
261
|
+
console.log("Network error:", error.message);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Advanced Options
|
|
267
|
+
|
|
268
|
+
Customize individual requests with options:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// Custom headers
|
|
272
|
+
const character = await client.character.get({
|
|
273
|
+
headers: {
|
|
274
|
+
"X-Custom-Header": "value",
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Request timeout (in milliseconds)
|
|
279
|
+
const skills = await client.character.skills.get({
|
|
280
|
+
timeout: 5000, // 5 second timeout
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Abort signal for cancellation
|
|
284
|
+
const controller = new AbortController();
|
|
285
|
+
const promise = client.faction.members.get({
|
|
286
|
+
signal: controller.signal,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Cancel the request
|
|
290
|
+
controller.abort();
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Timestamp Utility - Combine Galactic Time (CGT)
|
|
294
|
+
|
|
295
|
+
Convert between Unix timestamps, JavaScript Dates, and Star Wars Combine Galactic Time:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { Timestamp } from "swcombine.js";
|
|
299
|
+
|
|
300
|
+
// Get current CGT
|
|
301
|
+
const now = Timestamp.now();
|
|
302
|
+
console.log(now.toString()); // "Year 27 Day 134, 8:45:23"
|
|
303
|
+
|
|
304
|
+
// Convert from Unix timestamp
|
|
305
|
+
const timestamp = Timestamp.fromUnixTimestamp(1735920000);
|
|
306
|
+
console.log(timestamp.toString("day")); // "Year 27 Day 12"
|
|
307
|
+
|
|
308
|
+
// Convert from Date
|
|
309
|
+
const date = new Date();
|
|
310
|
+
const cgt = Timestamp.fromDate(date);
|
|
311
|
+
|
|
312
|
+
// Create specific CGT moment
|
|
313
|
+
const moment = new Timestamp({
|
|
314
|
+
year: 25,
|
|
315
|
+
day: 60,
|
|
316
|
+
hour: 12,
|
|
317
|
+
minute: 30,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Access components
|
|
321
|
+
console.log(moment.getYear()); // 25
|
|
322
|
+
console.log(moment.getDay()); // 60
|
|
323
|
+
console.log(moment.getHour()); // 12
|
|
324
|
+
console.log(moment.getMinute()); // 30
|
|
325
|
+
|
|
326
|
+
// Time arithmetic
|
|
327
|
+
const future = moment.add({ days: 5, hours: 3 });
|
|
328
|
+
const past = moment.subtract({ years: 1 });
|
|
329
|
+
|
|
330
|
+
// Get duration between timestamps
|
|
331
|
+
const duration = moment.getDurationTo(future);
|
|
332
|
+
console.log(duration); // { years: 0, days: 5, hours: 3, minutes: 0, seconds: 0 }
|
|
333
|
+
|
|
334
|
+
// Formatting options
|
|
335
|
+
now.toString("full"); // "Year 27 Day 134, 8:45:23"
|
|
336
|
+
now.toString("minute"); // "Year 27 Day 134, 8:45"
|
|
337
|
+
now.toString("day"); // "Year 27 Day 134"
|
|
338
|
+
now.toString("shortFull"); // "Y27 D134, 8:45:23"
|
|
339
|
+
now.toString("shortDay"); // "Y27 D134"
|
|
340
|
+
|
|
341
|
+
// Custom formatting with tags
|
|
342
|
+
now.toString("Day {d} of Year {y} at {hms}");
|
|
343
|
+
// "Day 134 of Year 27 at 08:45:23"
|
|
344
|
+
|
|
345
|
+
// Convert back to Unix/Date
|
|
346
|
+
const unixSeconds = moment.toUnixTimestamp("sec");
|
|
347
|
+
const unixMs = moment.toUnixTimestamp("ms");
|
|
348
|
+
const dateObj = moment.toDate();
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Custom Token Storage
|
|
352
|
+
|
|
353
|
+
By default, tokens are stored in memory. Implement custom storage for persistence:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { TokenStorage, StoredTokens } from "swcombine.js";
|
|
357
|
+
|
|
358
|
+
class FileTokenStorage implements TokenStorage {
|
|
359
|
+
async get(): Promise<StoredTokens | null> {
|
|
360
|
+
// Read from file, database, etc.
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async set(tokens: StoredTokens): Promise<void> {
|
|
364
|
+
// Save to file, database, etc.
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async clear(): Promise<void> {
|
|
368
|
+
// Clear stored tokens
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const oauth = new OAuthManager(config, new FileTokenStorage());
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Working with Scopes
|
|
376
|
+
|
|
377
|
+
All OAuth scopes are strongly typed using string literal union types. Get full IntelliSense and type safety with simple strings!
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { Scopes } from "swcombine.js";
|
|
381
|
+
import type { ScopeKey } from "swcombine.js";
|
|
382
|
+
|
|
383
|
+
// Use string literals with full type safety and IntelliSense
|
|
384
|
+
const myScopes: ScopeKey[] = [
|
|
385
|
+
'character_read',
|
|
386
|
+
'faction_members',
|
|
387
|
+
'messages_read',
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
// Get scope information programmatically
|
|
391
|
+
const scope = Scopes['character_read'];
|
|
392
|
+
console.log(scope.name); // "character_read"
|
|
393
|
+
console.log(scope.description); // "Read basic character information..."
|
|
394
|
+
console.log(scope.inherits); // [] (array of inherited scopes)
|
|
395
|
+
|
|
396
|
+
// Check what a scope inherits
|
|
397
|
+
const allScope = Scopes['character_all'];
|
|
398
|
+
console.log(allScope.inherits); // ['character_credits_write', ...]
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Available Scope Categories:**
|
|
402
|
+
- Character (read, stats, skills, credits, location, events, etc.)
|
|
403
|
+
- Messages (read, send, delete)
|
|
404
|
+
- Personal Inventory (ships, vehicles, stations, cities, facilities, planets, items, NPCs, droids, materials, creatures)
|
|
405
|
+
- Faction (read, members, stocks, credits, budgets, datacards)
|
|
406
|
+
- Faction Inventory (ships, vehicles, stations, cities, facilities, planets, items, NPCs, droids, materials, creatures)
|
|
407
|
+
|
|
408
|
+
See `src/auth/scopes.ts` for the complete list of 100+ available scopes.
|
|
409
|
+
|
|
410
|
+
## API Reference
|
|
411
|
+
|
|
412
|
+
### SWCClient
|
|
413
|
+
|
|
414
|
+
Main client for interacting with the Star Wars Combine API.
|
|
415
|
+
|
|
416
|
+
#### Constructor
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
new SWCClient(oauth: OAuthManager, baseUrl?: string)
|
|
420
|
+
new SWCClient(config: { oauth: OAuthManager, baseUrl?: string })
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### Resources
|
|
424
|
+
|
|
425
|
+
- `client.api` - API utility endpoints
|
|
426
|
+
- `helloWorld()` - Test endpoint (no auth required)
|
|
427
|
+
- `helloAuth()` - Test authenticated endpoint
|
|
428
|
+
- `permissions()` - List available OAuth permissions
|
|
429
|
+
- `rateLimits()` - Get current rate limit status
|
|
430
|
+
- `time()` - Get server time
|
|
431
|
+
|
|
432
|
+
- `client.character` - Character data
|
|
433
|
+
- `get()` - Get basic character info
|
|
434
|
+
- `creditlog.get()` - Get credit transaction history
|
|
435
|
+
- `permissions.get()` - Get OAuth permissions
|
|
436
|
+
- `skills.get()` - Get character skills
|
|
437
|
+
- `privileges.get()` - Get all privileges
|
|
438
|
+
- `privilege.get(id)` - Get specific privilege
|
|
439
|
+
- `privilege.post(id, data)` - Grant or revoke a privilege (requires `character_privileges`)
|
|
440
|
+
- `credits.get()` - Get current credits
|
|
441
|
+
- `credits.post(data)` - Transfer character credits (requires `character_credits_write`)
|
|
442
|
+
- `messages.get(id?)` - Get messages or specific message
|
|
443
|
+
- `messages.put(data)` - Send a message (requires `messages_send`)
|
|
444
|
+
- `messages.delete(id)` - Delete a message (requires `messages_delete`)
|
|
445
|
+
- `handlecheck.get(handle)` - Get character UID by handle (no authentication required)
|
|
446
|
+
|
|
447
|
+
- `client.faction` - Faction data
|
|
448
|
+
- `get(id?)` - Get faction info (your faction or specific ID)
|
|
449
|
+
- `budgets.get()` - Get all budgets
|
|
450
|
+
- `budget.get(id)` - Get specific budget
|
|
451
|
+
- `creditlog.get()` - Get credit transaction history
|
|
452
|
+
- `members.get()` - Get faction members
|
|
453
|
+
- `stockholders.get()` - Get stockholders
|
|
454
|
+
- `credits.get()` - Get faction credits
|
|
455
|
+
- `credits.post(data)` - Transfer faction credits (requires `faction_credits_write` permission)
|
|
456
|
+
|
|
457
|
+
- `client.inventory` - Inventory data
|
|
458
|
+
- `get(uid)` - Get inventory overview for character or faction
|
|
459
|
+
- Entity type resources (ships, vehicles, stations, cities, facilities, planets, items, npcs, droids, creatures, materials):
|
|
460
|
+
- `<entityType>.get(uid, assignType?, options?)` - List entities with optional filters
|
|
461
|
+
- Entity-specific resources:
|
|
462
|
+
- `entity.<entityType>.get(entityUid)` - Get specific entity details
|
|
463
|
+
- `entity.<entityType>.properties.get(entityUid)` - Get entity properties
|
|
464
|
+
- `entity.<entityType>.properties.post(entityUid, data)` - Set entity properties
|
|
465
|
+
- `entity.<entityType>.tags.get(entityUid)` - Get entity tags
|
|
466
|
+
- `entity.<entityType>.tags.post(entityUid, data)` - Add entity tags
|
|
467
|
+
- `entity.<entityType>.tags.delete(entityUid, data)` - Remove entity tags
|
|
468
|
+
|
|
469
|
+
### OAuthManager
|
|
470
|
+
|
|
471
|
+
Main class for handling OAuth flows.
|
|
472
|
+
|
|
473
|
+
#### Constructor
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
new OAuthManager(config: OAuthConfig, storage?: TokenStorage)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### Methods
|
|
480
|
+
|
|
481
|
+
- `getAuthorizationUrl(options?)` - Generate authorization URL
|
|
482
|
+
- `handleCallback(code)` - Exchange authorization code for tokens
|
|
483
|
+
- `getAccessToken()` - Get valid access token (auto-refreshes)
|
|
484
|
+
- `isAuthenticated()` - Check if user is authenticated
|
|
485
|
+
- `getStoredTokens()` - Get stored tokens
|
|
486
|
+
- `setTokens(tokens)` - Manually set tokens
|
|
487
|
+
- `revoke()` - Revoke refresh token and clear storage
|
|
488
|
+
- `logout()` - Clear stored tokens without revoking
|
|
489
|
+
|
|
490
|
+
### Low-Level Functions
|
|
491
|
+
|
|
492
|
+
For manual control:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import {
|
|
496
|
+
generateAuthorizationUrl,
|
|
497
|
+
exchangeCodeForToken,
|
|
498
|
+
refreshAccessToken,
|
|
499
|
+
revokeToken,
|
|
500
|
+
} from "swcombine.js";
|
|
501
|
+
|
|
502
|
+
const url = generateAuthorizationUrl({
|
|
503
|
+
clientId: "...",
|
|
504
|
+
redirectUri: "...",
|
|
505
|
+
scopes: ['character_read', 'faction_members'],
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const tokens = await exchangeCodeForToken({
|
|
509
|
+
code: "...",
|
|
510
|
+
clientId: "...",
|
|
511
|
+
clientSecret: "...",
|
|
512
|
+
redirectUri: "...",
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const newTokens = await refreshAccessToken({
|
|
516
|
+
refreshToken: "...",
|
|
517
|
+
clientId: "...",
|
|
518
|
+
clientSecret: "...",
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await revokeToken({
|
|
522
|
+
token: "...",
|
|
523
|
+
clientId: "...",
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Examples
|
|
528
|
+
|
|
529
|
+
Run the OAuth example:
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
bun run example:oauth
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## Testing
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
bun test
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## Important Notes
|
|
542
|
+
|
|
543
|
+
### SWC Combine OAuth Quirks
|
|
544
|
+
|
|
545
|
+
The SWC Combine API has some non-standard OAuth behavior:
|
|
546
|
+
|
|
547
|
+
1. **Authorization header**: Uses `Authorization: OAuth TOKEN` instead of `Bearer`
|
|
548
|
+
2. **Refresh tokens**: Only returned on **first** token exchange when `access_type=offline`
|
|
549
|
+
3. **Consent persistence**: Subsequent requests with same scopes won't re-prompt users
|
|
550
|
+
|
|
551
|
+
## API Endpoint Implementation Status
|
|
552
|
+
|
|
553
|
+
This section tracks which API endpoints have been implemented in the SDK.
|
|
554
|
+
|
|
555
|
+
### API Resource (5/6 implemented - 83%)
|
|
556
|
+
- [x] `GET /api/helloworld` - `client.api.helloWorld()`
|
|
557
|
+
- [x] `GET /api/helloauth` - `client.api.helloAuth()`
|
|
558
|
+
- [x] `GET /api/permissions` - `client.api.permissions()`
|
|
559
|
+
- [x] `GET /api/ratelimits` - `client.api.rateLimits()`
|
|
560
|
+
- [x] `GET /api/time` - `client.api.time()`
|
|
561
|
+
- [ ] `POST /api/time` - Convert CGT/timestamps
|
|
562
|
+
|
|
563
|
+
### Character Resource (14/14 implemented - 100%) ✅
|
|
564
|
+
- [x] `GET /character` - `client.character.get()`
|
|
565
|
+
- [x] `GET /character/{uid}/creditlog` - `client.character.creditlog.get()`
|
|
566
|
+
- [x] `GET /character/{uid}/permissions` - `client.character.permissions.get()`
|
|
567
|
+
- [x] `GET /character/{uid}/skills` - `client.character.skills.get()`
|
|
568
|
+
- [x] `GET /character/{uid}/privileges` - `client.character.privileges.get()`
|
|
569
|
+
- [x] `GET /character/{uid}/privileges/{group}/{priv}` - `client.character.privilege.get(id)`
|
|
570
|
+
- [x] `POST /character/{uid}/privileges/{group}/{priv}` - `client.character.privilege.post(id, data)`
|
|
571
|
+
- [x] `GET /character/{uid}/credits` - `client.character.credits.get()`
|
|
572
|
+
- [x] `POST /character/{uid}/credits` - `client.character.credits.post(data)`
|
|
573
|
+
- [x] `GET /character/{uid}/messages` - `client.character.messages.get()`
|
|
574
|
+
- [x] `PUT /character/{uid}/messages` - `client.character.messages.put(data)`
|
|
575
|
+
- [x] `GET /character/{uid}/messages/{msguid}` - `client.character.messages.get(id)`
|
|
576
|
+
- [x] `DELETE /character/{uid}/messages/{msguid}` - `client.character.messages.delete(id)`
|
|
577
|
+
- [x] `GET /character/handlecheck/{handle}` - `client.character.handlecheck.get(handle)`
|
|
578
|
+
|
|
579
|
+
### Faction Resource (9/11 implemented - 82%)
|
|
580
|
+
- [x] `GET /faction` - `client.faction.get()`
|
|
581
|
+
- [x] `GET /faction/{uid}` - `client.faction.get(id)`
|
|
582
|
+
- [x] `GET /faction/{uid}/budgets` - `client.faction.budgets.get()`
|
|
583
|
+
- [x] `GET /faction/{uid}/budget/{budgetuid}` - `client.faction.budget.get(id)`
|
|
584
|
+
- [x] `GET /faction/{uid}/creditlog` - `client.faction.creditlog.get()`
|
|
585
|
+
- [x] `GET /faction/{uid}/members` - `client.faction.members.get()`
|
|
586
|
+
- [ ] `POST /faction/{uid}/members` - Update member info fields
|
|
587
|
+
- [x] `GET /faction/{uid}/stockholders` - `client.faction.stockholders.get()`
|
|
588
|
+
- [x] `GET /faction/{uid}/credits` - `client.faction.credits.get()`
|
|
589
|
+
- [x] `POST /faction/{uid}/credits` - `client.faction.credits.post(data)`
|
|
590
|
+
- [ ] `GET /factions` - List all factions
|
|
591
|
+
|
|
592
|
+
### Inventory Resource (3/11 implemented - 27%)
|
|
593
|
+
⚠️ **Note**: Current implementation uses different structure than API spec
|
|
594
|
+
|
|
595
|
+
- [x] `GET /inventory/{uid}` - `client.inventory.get(uid)`
|
|
596
|
+
- [x] `GET /inventory/{uid}/{entity_type}/{assign_type}` - `client.inventory.{entityType}.get(uid, assignType, options)`
|
|
597
|
+
- [x] `GET /inventory/{entity_type}/{uid}` - `client.inventory.entity.{entityType}.get(entityUid)`
|
|
598
|
+
- [ ] `GET /inventory/{entity_type}/{uid}/properties` - Get entity properties
|
|
599
|
+
- [ ] `POST /inventory/{entity_type}/{uid}/{property}` - Update specific property
|
|
600
|
+
- [ ] `GET /inventory/{entity_type}/{uid}/tags` - Get entity tags
|
|
601
|
+
- [ ] `PUT /inventory/{entity_type}/{uid}/tag/{tag}` - Add/modify tag
|
|
602
|
+
- [ ] `DELETE /inventory/{entity_type}/{uid}/tag/{tag}` - Remove tag
|
|
603
|
+
|
|
604
|
+
### Datacard Resource (0/4 implemented - 0%)
|
|
605
|
+
- [ ] `GET /datacard/{uid}` - Get datacard details
|
|
606
|
+
- [ ] `POST /datacard/{uid}` - Assign datacard
|
|
607
|
+
- [ ] `DELETE /datacard/{uid}` - Revoke datacard
|
|
608
|
+
- [ ] `GET /datacards/{uid}` - List faction datacards
|
|
609
|
+
|
|
610
|
+
### Events Resource (0/2 implemented - 0%)
|
|
611
|
+
- [ ] `GET /events/{event_mode}/{event_type?}` - Get events collection
|
|
612
|
+
- [ ] `GET /event/{uid}` - Get specific event
|
|
613
|
+
|
|
614
|
+
### Galaxy Resource (0/10 implemented - 0%)
|
|
615
|
+
- [ ] `GET /galaxy/cities` - List all cities
|
|
616
|
+
- [ ] `GET /galaxy/cities/{uid}` - Get city details
|
|
617
|
+
- [ ] `GET /galaxy/planets` - List all planets
|
|
618
|
+
- [ ] `GET /galaxy/planets/{uid}` - Get planet details
|
|
619
|
+
- [ ] `GET /galaxy/sectors` - List all sectors
|
|
620
|
+
- [ ] `GET /galaxy/sectors/{uid}` - Get sector details
|
|
621
|
+
- [ ] `GET /galaxy/stations` - List all stations
|
|
622
|
+
- [ ] `GET /galaxy/stations/{uid}` - Get station details
|
|
623
|
+
- [ ] `GET /galaxy/systems` - List all systems
|
|
624
|
+
- [ ] `GET /galaxy/systems/{uid}` - Get system details
|
|
625
|
+
|
|
626
|
+
### Implementation Summary
|
|
627
|
+
- **API Resource**: 5/6 endpoints (83%)
|
|
628
|
+
- **Character Resource**: 14/14 endpoints (100%) ✅
|
|
629
|
+
- **Faction Resource**: 9/11 endpoints (82%)
|
|
630
|
+
- **Inventory Resource**: 3/11 endpoints (27% - needs refactoring)
|
|
631
|
+
- **Datacard Resource**: 0/4 endpoints (0%)
|
|
632
|
+
- **Events Resource**: 0/2 endpoints (0%)
|
|
633
|
+
- **Galaxy Resource**: 0/10 endpoints (0%)
|
|
634
|
+
|
|
635
|
+
**Overall**: 31/58 endpoints implemented (53%)
|
|
636
|
+
|
|
637
|
+
## Development Roadmap
|
|
638
|
+
|
|
639
|
+
- [x] Phase 1: OAuth 2.0 Authentication
|
|
640
|
+
- [x] URL generation
|
|
641
|
+
- [x] Token exchange
|
|
642
|
+
- [x] Token refresh
|
|
643
|
+
- [x] Token revocation
|
|
644
|
+
- [x] Token management
|
|
645
|
+
- [x] Tests
|
|
646
|
+
- [x] Phase 2: HTTP Client
|
|
647
|
+
- [x] Resource-based client architecture
|
|
648
|
+
- [x] Automatic authentication
|
|
649
|
+
- [x] Comprehensive error handling
|
|
650
|
+
- [x] Rate limit tracking
|
|
651
|
+
- [x] Core resources (api, character, faction)
|
|
652
|
+
- [x] Inventory resource with 11 entity types
|
|
653
|
+
- [x] Advanced filtering with InventoryFilters
|
|
654
|
+
- [x] Entity-specific operations (properties, tags)
|
|
655
|
+
- [x] Tests
|
|
656
|
+
- [ ] Phase 3: API Resources & Types
|
|
657
|
+
- [ ] Additional resources (events, market, etc.)
|
|
658
|
+
- [ ] Parse response specs
|
|
659
|
+
- [ ] Type-safe response types
|
|
660
|
+
- [ ] Auto-generate from API spec
|
|
661
|
+
|
|
662
|
+
## License
|
|
663
|
+
|
|
664
|
+
MIT
|
|
665
|
+
|
|
666
|
+
## Links
|
|
667
|
+
|
|
668
|
+
- [SWC Combine API Documentation](https://www.swcombine.com/ws)
|
|
669
|
+
- [OAuth 2.0 Flow Documentation](https://www.swcombine.com/ws/developers/oauth2/flows/web-server/)
|