@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/CapacitorPlayGames.podspec +17 -0
  3. package/LICENSE +22 -0
  4. package/Package.swift +36 -0
  5. package/README.md +611 -0
  6. package/android/build.gradle +76 -0
  7. package/android/consumer-rules.pro +4 -0
  8. package/android/proguard-rules.pro +2 -0
  9. package/android/src/main/AndroidManifest.xml +2 -0
  10. package/android/src/main/java/com/idleflowgames/playgames/AchievementsModule.kt +26 -0
  11. package/android/src/main/java/com/idleflowgames/playgames/LeaderboardsModule.kt +27 -0
  12. package/android/src/main/java/com/idleflowgames/playgames/Pgs.kt +48 -0
  13. package/android/src/main/java/com/idleflowgames/playgames/PlayGamesPlugin.kt +88 -0
  14. package/android/src/main/java/com/idleflowgames/playgames/SavedGamesModule.kt +119 -0
  15. package/android/src/main/java/com/idleflowgames/playgames/SignInModule.kt +83 -0
  16. package/dist/esm/definitions.d.ts +227 -0
  17. package/dist/esm/definitions.d.ts.map +1 -0
  18. package/dist/esm/definitions.js +2 -0
  19. package/dist/esm/definitions.js.map +1 -0
  20. package/dist/esm/index.d.ts +5 -0
  21. package/dist/esm/index.d.ts.map +1 -0
  22. package/dist/esm/index.js +7 -0
  23. package/dist/esm/index.js.map +1 -0
  24. package/dist/esm/web.d.ts +33 -0
  25. package/dist/esm/web.d.ts.map +1 -0
  26. package/dist/esm/web.js +45 -0
  27. package/dist/esm/web.js.map +1 -0
  28. package/dist/plugin.cjs.js +59 -0
  29. package/dist/plugin.cjs.js.map +1 -0
  30. package/dist/plugin.js +62 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/ios/Plugin/AchievementsModule.swift +73 -0
  33. package/ios/Plugin/LeaderboardsModule.swift +75 -0
  34. package/ios/Plugin/Pgs.swift +40 -0
  35. package/ios/Plugin/Plugin.swift +74 -0
  36. package/ios/Plugin/SavedGamesModule.swift +102 -0
  37. package/ios/Plugin/SignInModule.swift +139 -0
  38. 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&lt;<a href="#signinresult">SignInResult</a>&gt;</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&lt;{ signedIn: boolean; }&gt;</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&lt;<a href="#playerinfo">PlayerInfo</a>&gt;</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&lt;{ authCode: string; }&gt;</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&lt;<a href="#identityverificationsignature">IdentityVerificationSignature</a>&gt;</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&lt;{ snapshot: <a href="#snapshot">Snapshot</a> | null; }&gt;</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&lt;{ snapshots: SnapshotMeta[]; }&gt;</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>) =&gt; void</code> |
494
+
495
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</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>() =&gt; Promise&lt;void&gt;</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.** { *; }
@@ -0,0 +1,2 @@
1
+ # Project-specific ProGuard/R8 rules for the library's own release build.
2
+ # Consumer-facing keep rules live in consumer-rules.pro.
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />