@modbender/capacitor-play-games 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.
- package/CHANGELOG.md +86 -0
- package/CapacitorPlayGames.podspec +17 -0
- package/LICENSE +22 -0
- package/Package.swift +36 -0
- package/README.md +611 -0
- package/android/build.gradle +76 -0
- package/android/consumer-rules.pro +4 -0
- package/android/proguard-rules.pro +2 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/idleflowgames/playgames/AchievementsModule.kt +26 -0
- package/android/src/main/java/com/idleflowgames/playgames/LeaderboardsModule.kt +27 -0
- package/android/src/main/java/com/idleflowgames/playgames/Pgs.kt +48 -0
- package/android/src/main/java/com/idleflowgames/playgames/PlayGamesPlugin.kt +88 -0
- package/android/src/main/java/com/idleflowgames/playgames/SavedGamesModule.kt +119 -0
- package/android/src/main/java/com/idleflowgames/playgames/SignInModule.kt +83 -0
- package/dist/esm/definitions.d.ts +227 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +33 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +45 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +59 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +62 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/AchievementsModule.swift +73 -0
- package/ios/Plugin/LeaderboardsModule.swift +75 -0
- package/ios/Plugin/Pgs.swift +40 -0
- package/ios/Plugin/Plugin.swift +74 -0
- package/ios/Plugin/SavedGamesModule.swift +102 -0
- package/ios/Plugin/SignInModule.swift +139 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
# @modbender/capacitor-play-games
|
|
2
|
+
|
|
3
|
+
Capacitor 8 plugin for platform games services: **Google Play Games Services
|
|
4
|
+
(PGS v2)** on Android and **Apple GameKit / Game Center** on iOS, with a safe
|
|
5
|
+
no-op fallback on web. One TypeScript API covers sign-in, achievements,
|
|
6
|
+
leaderboards, and saved games across both platforms.
|
|
7
|
+
|
|
8
|
+
## About this fork
|
|
9
|
+
|
|
10
|
+
This is a fork of [`@idleflowgames/capacitor-play-games`](https://www.npmjs.com/package/@idleflowgames/capacitor-play-games)
|
|
11
|
+
0.2.1, MIT-licensed.
|
|
12
|
+
|
|
13
|
+
Upstream's GitHub repository — `github.com/idleflowgames/capacitor-play-games`,
|
|
14
|
+
the URL its own `package.json` still points at — returned 404 on 2026-09-08,
|
|
15
|
+
while the npm package remained published. The source here was recovered from
|
|
16
|
+
that published tarball rather than forked through GitHub. The Kotlin, Swift,
|
|
17
|
+
Gradle and podspec files ship in the tarball and are vendored byte-for-byte. The
|
|
18
|
+
TypeScript layer does **not** ship — the package carries `dist/` only, and its
|
|
19
|
+
sourcemaps set `sourcesContent: false` — so `src/` here was reconstructed from
|
|
20
|
+
`dist/esm/*.js` plus the emitted `.d.ts`. The declarations retained every doc
|
|
21
|
+
comment, which makes that reconstruction faithful rather than a rewrite.
|
|
22
|
+
|
|
23
|
+
That claim is checked rather than asserted: building `src/` here reproduces
|
|
24
|
+
upstream's published `dist/` exactly — all six emitted files, both the `.js` and
|
|
25
|
+
the `.d.ts`, are identical to the ones in the 0.2.1 tarball once formatting is
|
|
26
|
+
normalised.
|
|
27
|
+
|
|
28
|
+
### Changes from upstream 0.2.1
|
|
29
|
+
|
|
30
|
+
- **`android/build.gradle` now applies the Kotlin Android plugin.** Upstream puts
|
|
31
|
+
`kotlin-gradle-plugin` on the buildscript classpath and uses a
|
|
32
|
+
`kotlin { compilerOptions { ... } }` block, but never applies the plugin, so
|
|
33
|
+
Gradle rejects that block with `Could not find method kotlin()` before
|
|
34
|
+
compiling any source. One line.
|
|
35
|
+
- Renamed to the `@modbender` scope, and the SwiftPM product renamed with it —
|
|
36
|
+
Capacitor derives that product name from the full scoped package name, so the
|
|
37
|
+
two cannot drift apart. See the comment in `Package.swift`.
|
|
38
|
+
|
|
39
|
+
Deliberately unchanged: the Android namespace is still
|
|
40
|
+
`com.idleflowgames.playgames`. Keeping it means this tree diffs cleanly against
|
|
41
|
+
the 0.2.1 tarball, so a reviewer can confirm the Android delta is exactly the one
|
|
42
|
+
line above. Renaming it is a mechanical follow-up, not a blocker.
|
|
43
|
+
|
|
44
|
+
### Status
|
|
45
|
+
|
|
46
|
+
**Android is verified.** The plugin module compiles and assembles against a
|
|
47
|
+
real Capacitor 8 app — `:…-capacitor-play-games:assembleDebug` is BUILD
|
|
48
|
+
SUCCESSFUL on Gradle 8.14.3, Android Gradle Plugin 8.13.0 and JDK 21.
|
|
49
|
+
|
|
50
|
+
The Gradle fix is confirmed by reproduction rather than by reading: revert that
|
|
51
|
+
one line, rebuild, and the build fails with exactly the error upstream's open
|
|
52
|
+
issue reports —
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Could not find method kotlin() for arguments [...] on project
|
|
56
|
+
':modbender-capacitor-play-games' of type org.gradle.api.Project.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Worth knowing: the module keeps upstream's own buildscript classpath pinning AGP
|
|
60
|
+
9.3.1 and Kotlin 2.4.10, which is *higher* than the consuming app's AGP 8.13.0.
|
|
61
|
+
That combination was expected to be a second conflict and is not — it resolves
|
|
62
|
+
and builds green as-is.
|
|
63
|
+
|
|
64
|
+
The TypeScript builds and typechecks clean on TypeScript 7.0.2. **iOS remains
|
|
65
|
+
untested**; nothing here has been through Xcode.
|
|
66
|
+
|
|
67
|
+
The original MIT copyright is retained in [LICENSE](./LICENSE) alongside this
|
|
68
|
+
fork's.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bun add @modbender/capacitor-play-games
|
|
74
|
+
bunx cap sync
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Supported platforms
|
|
78
|
+
|
|
79
|
+
| Platform | Backing API | Notes |
|
|
80
|
+
| -------- | ---------------------------------------------------- | ----------------------------------------------------------- |
|
|
81
|
+
| Android | Google Play Games Services v2 (`play-services-games-v2`) | Requires PGS configured in the Google Play Console. |
|
|
82
|
+
| iOS | Apple GameKit / Game Center | Requires the Game Center capability + App Store Connect setup. |
|
|
83
|
+
| Web | none | Every method resolves to a safe default (signed out, empty). |
|
|
84
|
+
|
|
85
|
+
## Platform setup
|
|
86
|
+
|
|
87
|
+
Achievement and leaderboard **ids are yours**: the plugin takes opaque id strings
|
|
88
|
+
and passes them straight through to the platform. Create them in the Google Play
|
|
89
|
+
Console (Android) and App Store Connect / Game Center (iOS), then pass the
|
|
90
|
+
matching id at each call site. The two platforms issue different ids for the same
|
|
91
|
+
logical achievement, so keep a per-platform map in your app.
|
|
92
|
+
|
|
93
|
+
### Android
|
|
94
|
+
|
|
95
|
+
Configure Play Games Services v2 in the Google Play Console, then wire your
|
|
96
|
+
project id into Android resources and the app manifest (see Google's
|
|
97
|
+
[Play Games Services docs](https://developer.android.com/games/pgs)):
|
|
98
|
+
|
|
99
|
+
```xml
|
|
100
|
+
<!-- android/app/src/main/res/values/games-ids.xml -->
|
|
101
|
+
<resources>
|
|
102
|
+
<string name="game_services_project_id" translatable="false">YOUR_PGS_PROJECT_ID</string>
|
|
103
|
+
</resources>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```xml
|
|
107
|
+
<!-- inside <application> in android/app/src/main/AndroidManifest.xml -->
|
|
108
|
+
<meta-data
|
|
109
|
+
android:name="com.google.android.gms.games.APP_ID"
|
|
110
|
+
android:value="@string/game_services_project_id" />
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`google-services.json` is not required for PGS v2 on its own; it is only needed
|
|
114
|
+
if you also wire Firebase.
|
|
115
|
+
|
|
116
|
+
### iOS
|
|
117
|
+
|
|
118
|
+
Enable the **Game Center** capability on your app target in Xcode and create the
|
|
119
|
+
app's achievements / leaderboards in App Store Connect. The system presents the
|
|
120
|
+
Game Center sign-in UI.
|
|
121
|
+
|
|
122
|
+
> If your app shows an App Tracking Transparency (ATT) prompt, request it
|
|
123
|
+
> **before** calling `initialize()`. iOS suppresses an ATT prompt shown while
|
|
124
|
+
> Game Center's "Welcome back" banner is on screen, and `initialize()` is what
|
|
125
|
+
> can trigger that banner.
|
|
126
|
+
|
|
127
|
+
## Usage
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { PlayGames } from "@modbender/capacitor-play-games";
|
|
131
|
+
|
|
132
|
+
await PlayGames.initialize();
|
|
133
|
+
|
|
134
|
+
const { signedIn } = await PlayGames.signIn(); // silent by default
|
|
135
|
+
if (!signedIn) {
|
|
136
|
+
// Force the interactive flow from an explicit user gesture:
|
|
137
|
+
await PlayGames.signIn({ silent: false });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await PlayGames.unlockAchievement({ id: platformAchievementId });
|
|
141
|
+
await PlayGames.submitScore({ leaderboardId: platformLeaderboardId, score: 1234 });
|
|
142
|
+
|
|
143
|
+
// Cross-device saves:
|
|
144
|
+
await PlayGames.saveSnapshot({ name: "main", data: JSON.stringify(state) });
|
|
145
|
+
const { snapshot } = await PlayGames.loadSnapshot({ name: "main" });
|
|
146
|
+
|
|
147
|
+
// React to system-driven sign-in changes (e.g. signed out via Settings):
|
|
148
|
+
await PlayGames.addListener("signInStateChanged", ({ signedIn }) => {
|
|
149
|
+
// update UI
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
On web every method resolves to a safe default, so gate feature usage behind
|
|
154
|
+
`isSignedIn()` rather than platform checks.
|
|
155
|
+
|
|
156
|
+
## API
|
|
157
|
+
|
|
158
|
+
<docgen-index>
|
|
159
|
+
|
|
160
|
+
* [`initialize()`](#initialize)
|
|
161
|
+
* [`signIn(...)`](#signin)
|
|
162
|
+
* [`isSignedIn()`](#issignedin)
|
|
163
|
+
* [`getPlayer()`](#getplayer)
|
|
164
|
+
* [`requestServerSideAccess(...)`](#requestserversideaccess)
|
|
165
|
+
* [`fetchIdentityVerificationSignature()`](#fetchidentityverificationsignature)
|
|
166
|
+
* [`unlockAchievement(...)`](#unlockachievement)
|
|
167
|
+
* [`incrementAchievement(...)`](#incrementachievement)
|
|
168
|
+
* [`showAchievements()`](#showachievements)
|
|
169
|
+
* [`submitScore(...)`](#submitscore)
|
|
170
|
+
* [`showLeaderboard(...)`](#showleaderboard)
|
|
171
|
+
* [`showAllLeaderboards()`](#showallleaderboards)
|
|
172
|
+
* [`loadSnapshot(...)`](#loadsnapshot)
|
|
173
|
+
* [`saveSnapshot(...)`](#savesnapshot)
|
|
174
|
+
* [`listSnapshots()`](#listsnapshots)
|
|
175
|
+
* [`deleteSnapshot(...)`](#deletesnapshot)
|
|
176
|
+
* [`addListener('signInStateChanged', ...)`](#addlistenersigninstatechanged-)
|
|
177
|
+
* [`removeAllListeners()`](#removealllisteners)
|
|
178
|
+
* [Interfaces](#interfaces)
|
|
179
|
+
* [Type Aliases](#type-aliases)
|
|
180
|
+
|
|
181
|
+
</docgen-index>
|
|
182
|
+
|
|
183
|
+
<docgen-api>
|
|
184
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
185
|
+
|
|
186
|
+
### initialize()
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
initialize() => Promise<void>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Initialize the native games SDK. Idempotent.
|
|
193
|
+
|
|
194
|
+
On Android this triggers `PlayGamesSdk.initialize`; on iOS it installs the
|
|
195
|
+
GameKit authentication handler. Call once, after any App Tracking
|
|
196
|
+
Transparency prompt has resolved, before the other methods.
|
|
197
|
+
|
|
198
|
+
**Since:** 0.1.0
|
|
199
|
+
|
|
200
|
+
--------------------
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
### signIn(...)
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
signIn(opts?: { silent?: boolean | undefined; } | undefined) => Promise<SignInResult>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Sign in to the platform games service.
|
|
210
|
+
|
|
211
|
+
`silent` (default `true`) attempts auto sign-in with no UI; on most devices
|
|
212
|
+
this succeeds if the player has previously authenticated this game. Pass
|
|
213
|
+
`silent: false` to force the full interactive flow, and only in response to
|
|
214
|
+
an explicit user gesture.
|
|
215
|
+
|
|
216
|
+
| Param | Type |
|
|
217
|
+
| ---------- | ---------------------------------- |
|
|
218
|
+
| **`opts`** | <code>{ silent?: boolean; }</code> |
|
|
219
|
+
|
|
220
|
+
**Returns:** <code>Promise<<a href="#signinresult">SignInResult</a>></code>
|
|
221
|
+
|
|
222
|
+
**Since:** 0.1.0
|
|
223
|
+
|
|
224
|
+
--------------------
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
### isSignedIn()
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
isSignedIn() => Promise<{ signedIn: boolean; }>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Whether a player is currently signed in.
|
|
234
|
+
|
|
235
|
+
**Returns:** <code>Promise<{ signedIn: boolean; }></code>
|
|
236
|
+
|
|
237
|
+
**Since:** 0.1.0
|
|
238
|
+
|
|
239
|
+
--------------------
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
### getPlayer()
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
getPlayer() => Promise<PlayerInfo>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Get the signed-in player's profile.
|
|
249
|
+
|
|
250
|
+
On Android and iOS this rejects when no player is signed in. On web (the
|
|
251
|
+
no-op fallback) it resolves an empty profile (`playerId: ""`).
|
|
252
|
+
|
|
253
|
+
**Returns:** <code>Promise<<a href="#playerinfo">PlayerInfo</a>></code>
|
|
254
|
+
|
|
255
|
+
**Since:** 0.1.0
|
|
256
|
+
|
|
257
|
+
--------------------
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
### requestServerSideAccess(...)
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
requestServerSideAccess(opts: { serverClientId: string; forceRefresh?: boolean; }) => Promise<{ authCode: string; }>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Request a one-time OAuth 2.0 server auth code for the signed-in Play Games
|
|
267
|
+
player, for a backend to exchange for the AUTHORITATIVE player id (Google Play
|
|
268
|
+
Games Services v2 `GamesSignInClient.requestServerSideAccess`).
|
|
269
|
+
|
|
270
|
+
`serverClientId` is the OAuth 2.0 **web** client id backing the game; the code
|
|
271
|
+
is redeemed against it server-side. `forceRefresh` (default `false`) requests a
|
|
272
|
+
fresh code even if one was recently granted.
|
|
273
|
+
|
|
274
|
+
Android only. iOS rejects (unimplemented); the web fallback resolves an empty
|
|
275
|
+
`authCode`.
|
|
276
|
+
|
|
277
|
+
| Param | Type |
|
|
278
|
+
| ---------- | ---------------------------------------------------------------- |
|
|
279
|
+
| **`opts`** | <code>{ serverClientId: string; forceRefresh?: boolean; }</code> |
|
|
280
|
+
|
|
281
|
+
**Returns:** <code>Promise<{ authCode: string; }></code>
|
|
282
|
+
|
|
283
|
+
**Since:** 0.2.0
|
|
284
|
+
|
|
285
|
+
--------------------
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
### fetchIdentityVerificationSignature()
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
fetchIdentityVerificationSignature() => Promise<IdentityVerificationSignature>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Fetch a GameKit identity-verification signature for the signed-in Game Center
|
|
295
|
+
player (`GKLocalPlayer.fetchItems(forIdentityVerificationSignature:)`). A
|
|
296
|
+
backend verifies the returned bundle against Apple's certificate to trust the
|
|
297
|
+
player id rather than the untrusted client's claim. Rejects when no player is
|
|
298
|
+
signed in.
|
|
299
|
+
|
|
300
|
+
iOS only. Android rejects (unimplemented); the web fallback resolves an empty
|
|
301
|
+
bundle.
|
|
302
|
+
|
|
303
|
+
**Returns:** <code>Promise<<a href="#identityverificationsignature">IdentityVerificationSignature</a>></code>
|
|
304
|
+
|
|
305
|
+
**Since:** 0.2.0
|
|
306
|
+
|
|
307
|
+
--------------------
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
### unlockAchievement(...)
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
unlockAchievement(opts: { id: string; }) => Promise<void>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Unlock an achievement by its platform id (Play Console achievement id on
|
|
317
|
+
Android, App Store Connect / Game Center id on iOS).
|
|
318
|
+
|
|
319
|
+
| Param | Type |
|
|
320
|
+
| ---------- | ---------------------------- |
|
|
321
|
+
| **`opts`** | <code>{ id: string; }</code> |
|
|
322
|
+
|
|
323
|
+
**Since:** 0.1.0
|
|
324
|
+
|
|
325
|
+
--------------------
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
### incrementAchievement(...)
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
incrementAchievement(opts: { id: string; steps: number; }) => Promise<void>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Increment a partial (incremental) achievement.
|
|
335
|
+
|
|
336
|
+
`steps` is interpreted differently per platform: on Android (PGS) it is a
|
|
337
|
+
discrete step count toward the achievement's Play Console step total; on
|
|
338
|
+
iOS (GameKit) it is added to `percentComplete` as percentage points.
|
|
339
|
+
Compute a platform-appropriate value (e.g. via `Capacitor.getPlatform()`)
|
|
340
|
+
so progress matches on both stores.
|
|
341
|
+
|
|
342
|
+
| Param | Type |
|
|
343
|
+
| ---------- | ------------------------------------------- |
|
|
344
|
+
| **`opts`** | <code>{ id: string; steps: number; }</code> |
|
|
345
|
+
|
|
346
|
+
**Since:** 0.1.0
|
|
347
|
+
|
|
348
|
+
--------------------
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
### showAchievements()
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
showAchievements() => Promise<void>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Show the platform's native achievements UI.
|
|
358
|
+
|
|
359
|
+
**Since:** 0.1.0
|
|
360
|
+
|
|
361
|
+
--------------------
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
### submitScore(...)
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
submitScore(opts: { leaderboardId: string; score: number; }) => Promise<void>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Submit a score to a leaderboard by its platform id.
|
|
371
|
+
|
|
372
|
+
| Param | Type |
|
|
373
|
+
| ---------- | ------------------------------------------------------ |
|
|
374
|
+
| **`opts`** | <code>{ leaderboardId: string; score: number; }</code> |
|
|
375
|
+
|
|
376
|
+
**Since:** 0.1.0
|
|
377
|
+
|
|
378
|
+
--------------------
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
### showLeaderboard(...)
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
showLeaderboard(opts: { leaderboardId: string; }) => Promise<void>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Show the native UI for a single leaderboard.
|
|
388
|
+
|
|
389
|
+
| Param | Type |
|
|
390
|
+
| ---------- | --------------------------------------- |
|
|
391
|
+
| **`opts`** | <code>{ leaderboardId: string; }</code> |
|
|
392
|
+
|
|
393
|
+
**Since:** 0.1.0
|
|
394
|
+
|
|
395
|
+
--------------------
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
### showAllLeaderboards()
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
showAllLeaderboards() => Promise<void>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Show the native all-leaderboards UI.
|
|
405
|
+
|
|
406
|
+
**Since:** 0.1.0
|
|
407
|
+
|
|
408
|
+
--------------------
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
### loadSnapshot(...)
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
loadSnapshot(opts: { name: string; }) => Promise<{ snapshot: Snapshot | null; }>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Load a saved-game snapshot by its stable name. Resolves `{ snapshot: null }`
|
|
418
|
+
when no snapshot exists for that name.
|
|
419
|
+
|
|
420
|
+
| Param | Type |
|
|
421
|
+
| ---------- | ------------------------------ |
|
|
422
|
+
| **`opts`** | <code>{ name: string; }</code> |
|
|
423
|
+
|
|
424
|
+
**Returns:** <code>Promise<{ snapshot: <a href="#snapshot">Snapshot</a> | null; }></code>
|
|
425
|
+
|
|
426
|
+
**Since:** 0.1.0
|
|
427
|
+
|
|
428
|
+
--------------------
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
### saveSnapshot(...)
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
saveSnapshot(opts: { name: string; data: string; description?: string; }) => Promise<void>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Create or overwrite a saved-game snapshot. Conflicts are auto-resolved by
|
|
438
|
+
most-recently-modified (last write wins), with no merge.
|
|
439
|
+
|
|
440
|
+
| Param | Type |
|
|
441
|
+
| ---------- | ------------------------------------------------------------------ |
|
|
442
|
+
| **`opts`** | <code>{ name: string; data: string; description?: string; }</code> |
|
|
443
|
+
|
|
444
|
+
**Since:** 0.1.0
|
|
445
|
+
|
|
446
|
+
--------------------
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
### listSnapshots()
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
listSnapshots() => Promise<{ snapshots: SnapshotMeta[]; }>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
List metadata for all of the player's snapshots.
|
|
456
|
+
|
|
457
|
+
**Returns:** <code>Promise<{ snapshots: SnapshotMeta[]; }></code>
|
|
458
|
+
|
|
459
|
+
**Since:** 0.1.0
|
|
460
|
+
|
|
461
|
+
--------------------
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
### deleteSnapshot(...)
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
deleteSnapshot(opts: { name: string; }) => Promise<void>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Delete a saved-game snapshot by its stable name.
|
|
471
|
+
|
|
472
|
+
| Param | Type |
|
|
473
|
+
| ---------- | ------------------------------ |
|
|
474
|
+
| **`opts`** | <code>{ name: string; }</code> |
|
|
475
|
+
|
|
476
|
+
**Since:** 0.1.0
|
|
477
|
+
|
|
478
|
+
--------------------
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
### addListener('signInStateChanged', ...)
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
addListener(event: "signInStateChanged", listener: (e: SignInStateChangedEvent) => void) => Promise<PluginListenerHandle>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Listen for sign-in state changes: an interactive sign-in completing, or the
|
|
488
|
+
player signing out of the platform service system-wide.
|
|
489
|
+
|
|
490
|
+
| Param | Type |
|
|
491
|
+
| -------------- | --------------------------------------------------------------------- |
|
|
492
|
+
| **`event`** | <code>'signInStateChanged'</code> |
|
|
493
|
+
| **`listener`** | <code>(e: <a href="#signinresult">SignInResult</a>) => void</code> |
|
|
494
|
+
|
|
495
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
496
|
+
|
|
497
|
+
**Since:** 0.1.0
|
|
498
|
+
|
|
499
|
+
--------------------
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
### removeAllListeners()
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
removeAllListeners() => Promise<void>
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Remove all listeners registered through this plugin.
|
|
509
|
+
|
|
510
|
+
**Since:** 0.1.0
|
|
511
|
+
|
|
512
|
+
--------------------
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
### Interfaces
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
#### SignInResult
|
|
519
|
+
|
|
520
|
+
Result of a sign-in attempt, or the payload of a sign-in state change.
|
|
521
|
+
|
|
522
|
+
| Prop | Type | Description |
|
|
523
|
+
| -------------- | ------------------------------------------------- | --------------------------------------------------------- |
|
|
524
|
+
| **`signedIn`** | <code>boolean</code> | Whether the player is currently authenticated. |
|
|
525
|
+
| **`player`** | <code><a href="#playerinfo">PlayerInfo</a></code> | The player profile, present only when `signedIn` is true. |
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
#### PlayerInfo
|
|
529
|
+
|
|
530
|
+
A signed-in player's public profile.
|
|
531
|
+
|
|
532
|
+
| Prop | Type | Description |
|
|
533
|
+
| ----------------- | ------------------- | ----------------------------------------------------------------------------- |
|
|
534
|
+
| **`playerId`** | <code>string</code> | Stable, platform-assigned player id (PGS player id / GameKit `gamePlayerID`). |
|
|
535
|
+
| **`displayName`** | <code>string</code> | Display name as shown in Google Play Games / Game Center. |
|
|
536
|
+
| **`avatarUrl`** | <code>string</code> | URL of the player's avatar image, when the platform exposes one. |
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
#### IdentityVerificationSignature
|
|
540
|
+
|
|
541
|
+
A GameKit identity-verification bundle
|
|
542
|
+
(`GKLocalPlayer.fetchItems(forIdentityVerificationSignature:)`). A third-party
|
|
543
|
+
server verifies `signature` against the certificate at `publicKeyUrl` to trust
|
|
544
|
+
the Game Center `playerId` without relaying it through the untrusted client.
|
|
545
|
+
|
|
546
|
+
| Prop | Type | Description |
|
|
547
|
+
| ------------------ | ------------------- | ----------------------------------------------------------------------------- |
|
|
548
|
+
| **`publicKeyUrl`** | <code>string</code> | URL of Apple's public-key certificate used to verify `signature`. |
|
|
549
|
+
| **`signature`** | <code>string</code> | Base64-encoded signature over the verification payload. |
|
|
550
|
+
| **`salt`** | <code>string</code> | Base64-encoded random salt Apple mixed into the signed payload. |
|
|
551
|
+
| **`timestamp`** | <code>number</code> | Signature creation time, in epoch milliseconds (check freshness server-side). |
|
|
552
|
+
| **`playerId`** | <code>string</code> | The player id the signature attests (GameKit `gamePlayerID`). |
|
|
553
|
+
| **`bundleId`** | <code>string</code> | The app's bundle id, part of the signed payload. |
|
|
554
|
+
| **`teamPlayerId`** | <code>string</code> | GameKit `teamPlayerID` (stable across the team's games), when available. |
|
|
555
|
+
| **`gamePlayerId`** | <code>string</code> | GameKit `gamePlayerID` (stable per game), when available. |
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
#### Snapshot
|
|
559
|
+
|
|
560
|
+
A saved-game snapshot together with its serialized payload.
|
|
561
|
+
|
|
562
|
+
| Prop | Type | Description |
|
|
563
|
+
| ----------------- | ------------------- | ----------------------------------------------------------------------- |
|
|
564
|
+
| **`name`** | <code>string</code> | Stable unique name the snapshot was saved under. |
|
|
565
|
+
| **`description`** | <code>string</code> | Human-readable description stored with the snapshot. |
|
|
566
|
+
| **`modifiedAt`** | <code>number</code> | Last-modified time, in epoch milliseconds. |
|
|
567
|
+
| **`data`** | <code>string</code> | The serialized save payload as a UTF-8 string (encode binary yourself). |
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
#### SnapshotMeta
|
|
571
|
+
|
|
572
|
+
<a href="#snapshot">Snapshot</a> metadata without the payload, as returned by `listSnapshots`.
|
|
573
|
+
|
|
574
|
+
| Prop | Type | Description |
|
|
575
|
+
| ----------------- | ------------------- | ---------------------------------------------------- |
|
|
576
|
+
| **`name`** | <code>string</code> | Stable unique name of the snapshot. |
|
|
577
|
+
| **`description`** | <code>string</code> | Human-readable description stored with the snapshot. |
|
|
578
|
+
| **`modifiedAt`** | <code>number</code> | Last-modified time, in epoch milliseconds. |
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
#### PluginListenerHandle
|
|
582
|
+
|
|
583
|
+
| Prop | Type |
|
|
584
|
+
| ------------ | ----------------------------------------- |
|
|
585
|
+
| **`remove`** | <code>() => Promise<void></code> |
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
### Type Aliases
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
#### SignInStateChangedEvent
|
|
592
|
+
|
|
593
|
+
Payload of the `signInStateChanged` event.
|
|
594
|
+
|
|
595
|
+
<code><a href="#signinresult">SignInResult</a></code>
|
|
596
|
+
|
|
597
|
+
</docgen-api>
|
|
598
|
+
|
|
599
|
+
## Development
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
bun install
|
|
603
|
+
bun run verify # typecheck + build
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
The TypeScript bridge is built to `dist/` (ESM + CJS + types). The native sources
|
|
607
|
+
under `android/` and `ios/` ship in the package and are wired up by `npx cap sync`.
|
|
608
|
+
|
|
609
|
+
## License
|
|
610
|
+
|
|
611
|
+
[MIT](./LICENSE) © Idle Flow Games (original), © modbender (fork)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
2
|
+
|
|
3
|
+
ext {
|
|
4
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
5
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
6
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
7
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
8
|
+
// play-services-games-v2:22.0.0 raises the SDK's own minSdk to 24, which is
|
|
9
|
+
// why this module's minSdkVersion floor below cannot drop back to 21.
|
|
10
|
+
playServicesGamesV2Version = '22.0.0'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
buildscript {
|
|
14
|
+
repositories {
|
|
15
|
+
google()
|
|
16
|
+
mavenCentral()
|
|
17
|
+
}
|
|
18
|
+
dependencies {
|
|
19
|
+
classpath 'com.android.tools.build:gradle:9.3.1'
|
|
20
|
+
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.10'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
apply plugin: 'com.android.library'
|
|
25
|
+
// The module is written in Kotlin, so the Kotlin Android plugin has to be
|
|
26
|
+
// applied, not merely present on the buildscript classpath. Without this line
|
|
27
|
+
// the kotlin {} block below is an unknown method and the build fails before
|
|
28
|
+
// any source is compiled.
|
|
29
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
30
|
+
|
|
31
|
+
android {
|
|
32
|
+
namespace = "com.idleflowgames.playgames"
|
|
33
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
34
|
+
defaultConfig {
|
|
35
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
36
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
37
|
+
versionCode 1
|
|
38
|
+
versionName "0.1.0"
|
|
39
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
40
|
+
consumerProguardFiles 'consumer-rules.pro'
|
|
41
|
+
}
|
|
42
|
+
buildTypes {
|
|
43
|
+
release {
|
|
44
|
+
minifyEnabled false
|
|
45
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
lint {
|
|
49
|
+
abortOnError = false
|
|
50
|
+
}
|
|
51
|
+
compileOptions {
|
|
52
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
53
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
kotlin {
|
|
58
|
+
compilerOptions {
|
|
59
|
+
jvmTarget = JvmTarget.JVM_21
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
repositories {
|
|
64
|
+
google()
|
|
65
|
+
mavenCentral()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dependencies {
|
|
69
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
70
|
+
implementation project(':capacitor-android')
|
|
71
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
72
|
+
implementation "com.google.android.gms:play-services-games-v2:$playServicesGamesV2Version"
|
|
73
|
+
testImplementation "junit:junit:$junitVersion"
|
|
74
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
75
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
76
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# Consumer ProGuard/R8 rules applied to apps that depend on this plugin.
|
|
2
|
+
# The Capacitor bridge instantiates the plugin reflectively by class name, so
|
|
3
|
+
# keep the plugin and its native modules even under full minification.
|
|
4
|
+
-keep class com.idleflowgames.playgames.** { *; }
|