@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
@@ -0,0 +1,33 @@
1
+ import { WebPlugin } from "@capacitor/core";
2
+ import type { IdentityVerificationSignature, PlayerInfo, PlayGamesPlugin, SignInResult, Snapshot, SnapshotMeta } from "./definitions";
3
+ /**
4
+ * Web / non-native fallback: every method resolves to a safe default (signed
5
+ * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.
6
+ */
7
+ export declare class PlayGamesWeb extends WebPlugin implements PlayGamesPlugin {
8
+ initialize(): Promise<void>;
9
+ signIn(): Promise<SignInResult>;
10
+ isSignedIn(): Promise<{
11
+ signedIn: boolean;
12
+ }>;
13
+ getPlayer(): Promise<PlayerInfo>;
14
+ requestServerSideAccess(): Promise<{
15
+ authCode: string;
16
+ }>;
17
+ fetchIdentityVerificationSignature(): Promise<IdentityVerificationSignature>;
18
+ unlockAchievement(): Promise<void>;
19
+ incrementAchievement(): Promise<void>;
20
+ showAchievements(): Promise<void>;
21
+ submitScore(): Promise<void>;
22
+ showLeaderboard(): Promise<void>;
23
+ showAllLeaderboards(): Promise<void>;
24
+ loadSnapshot(): Promise<{
25
+ snapshot: Snapshot | null;
26
+ }>;
27
+ saveSnapshot(): Promise<void>;
28
+ listSnapshots(): Promise<{
29
+ snapshots: SnapshotMeta[];
30
+ }>;
31
+ deleteSnapshot(): Promise<void>;
32
+ }
33
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EACV,6BAA6B,EAC7B,UAAU,EACV,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,YAAY,EACb,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,qBAAa,YAAa,SAAQ,SAAU,YAAW,eAAe;IAC9D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAE9B,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC,CAEpC;IAEK,UAAU,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAEjD;IAEK,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CAErC;IAEK,uBAAuB,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAE7D;IAEK,kCAAkC,IAAI,OAAO,CAAC,6BAA6B,CAAC,CASjF;IAEK,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAErC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAExC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEpC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAE/B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEnC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEvC,YAAY,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;KAAE,CAAC,CAE3D;IAEK,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;IAEhC,aAAa,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC,CAE5D;IAEK,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;CACzC"}
@@ -0,0 +1,45 @@
1
+ import { WebPlugin } from "@capacitor/core";
2
+ /**
3
+ * Web / non-native fallback: every method resolves to a safe default (signed
4
+ * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.
5
+ */
6
+ export class PlayGamesWeb extends WebPlugin {
7
+ async initialize() { }
8
+ async signIn() {
9
+ return { signedIn: false };
10
+ }
11
+ async isSignedIn() {
12
+ return { signedIn: false };
13
+ }
14
+ async getPlayer() {
15
+ return { playerId: "", displayName: "" };
16
+ }
17
+ async requestServerSideAccess() {
18
+ return { authCode: "" };
19
+ }
20
+ async fetchIdentityVerificationSignature() {
21
+ return {
22
+ publicKeyUrl: "",
23
+ signature: "",
24
+ salt: "",
25
+ timestamp: 0,
26
+ playerId: "",
27
+ bundleId: "",
28
+ };
29
+ }
30
+ async unlockAchievement() { }
31
+ async incrementAchievement() { }
32
+ async showAchievements() { }
33
+ async submitScore() { }
34
+ async showLeaderboard() { }
35
+ async showAllLeaderboards() { }
36
+ async loadSnapshot() {
37
+ return { snapshot: null };
38
+ }
39
+ async saveSnapshot() { }
40
+ async listSnapshots() {
41
+ return { snapshots: [] };
42
+ }
43
+ async deleteSnapshot() { }
44
+ }
45
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAW5C;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,SAAS;IACzC,KAAK,CAAC,UAAU,KAAmB,CAAC;IAEpC,KAAK,CAAC,MAAM;QACV,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,kCAAkC;QACtC,OAAO;YACL,YAAY,EAAE,EAAE;YAChB,SAAS,EAAE,EAAE;YACb,IAAI,EAAE,EAAE;YACR,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,EAAE;YACZ,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iBAAiB,KAAmB,CAAC;IAE3C,KAAK,CAAC,oBAAoB,KAAmB,CAAC;IAE9C,KAAK,CAAC,gBAAgB,KAAmB,CAAC;IAE1C,KAAK,CAAC,WAAW,KAAmB,CAAC;IAErC,KAAK,CAAC,eAAe,KAAmB,CAAC;IAEzC,KAAK,CAAC,mBAAmB,KAAmB,CAAC;IAE7C,KAAK,CAAC,YAAY;QAChB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,YAAY,KAAmB,CAAC;IAEtC,KAAK,CAAC,aAAa;QACjB,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,cAAc,KAAmB,CAAC;CACzC"}
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ const PlayGames = core.registerPlugin("PlayGames", {
6
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.PlayGamesWeb()),
7
+ });
8
+
9
+ /**
10
+ * Web / non-native fallback: every method resolves to a safe default (signed
11
+ * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.
12
+ */
13
+ class PlayGamesWeb extends core.WebPlugin {
14
+ async initialize() { }
15
+ async signIn() {
16
+ return { signedIn: false };
17
+ }
18
+ async isSignedIn() {
19
+ return { signedIn: false };
20
+ }
21
+ async getPlayer() {
22
+ return { playerId: "", displayName: "" };
23
+ }
24
+ async requestServerSideAccess() {
25
+ return { authCode: "" };
26
+ }
27
+ async fetchIdentityVerificationSignature() {
28
+ return {
29
+ publicKeyUrl: "",
30
+ signature: "",
31
+ salt: "",
32
+ timestamp: 0,
33
+ playerId: "",
34
+ bundleId: "",
35
+ };
36
+ }
37
+ async unlockAchievement() { }
38
+ async incrementAchievement() { }
39
+ async showAchievements() { }
40
+ async submitScore() { }
41
+ async showLeaderboard() { }
42
+ async showAllLeaderboards() { }
43
+ async loadSnapshot() {
44
+ return { snapshot: null };
45
+ }
46
+ async saveSnapshot() { }
47
+ async listSnapshots() {
48
+ return { snapshots: [] };
49
+ }
50
+ async deleteSnapshot() { }
51
+ }
52
+
53
+ var web = /*#__PURE__*/Object.freeze({
54
+ __proto__: null,
55
+ PlayGamesWeb: PlayGamesWeb
56
+ });
57
+
58
+ exports.PlayGames = PlayGames;
59
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nconst PlayGames = registerPlugin(\"PlayGames\", {\n web: () => import(\"./web\").then((m) => new m.PlayGamesWeb()),\n});\nexport * from \"./definitions\";\nexport { PlayGames };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from \"@capacitor/core\";\n/**\n * Web / non-native fallback: every method resolves to a safe default (signed\n * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.\n */\nexport class PlayGamesWeb extends WebPlugin {\n async initialize() { }\n async signIn() {\n return { signedIn: false };\n }\n async isSignedIn() {\n return { signedIn: false };\n }\n async getPlayer() {\n return { playerId: \"\", displayName: \"\" };\n }\n async requestServerSideAccess() {\n return { authCode: \"\" };\n }\n async fetchIdentityVerificationSignature() {\n return {\n publicKeyUrl: \"\",\n signature: \"\",\n salt: \"\",\n timestamp: 0,\n playerId: \"\",\n bundleId: \"\",\n };\n }\n async unlockAchievement() { }\n async incrementAchievement() { }\n async showAchievements() { }\n async submitScore() { }\n async showLeaderboard() { }\n async showAllLeaderboards() { }\n async loadSnapshot() {\n return { snapshot: null };\n }\n async saveSnapshot() { }\n async listSnapshots() {\n return { snapshots: [] };\n }\n async deleteSnapshot() { }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,SAAS,GAAGA,mBAAc,CAAC,WAAW,EAAE;AAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;AAChE,CAAC;;ACFD;AACA;AACA;AACA;AACO,MAAM,YAAY,SAASC,cAAS,CAAC;AAC5C,IAAI,MAAM,UAAU,GAAG,EAAE;AACzB,IAAI,MAAM,MAAM,GAAG;AACnB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;AAClC,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;AAClC,IAAI;AACJ,IAAI,MAAM,SAAS,GAAG;AACtB,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;AAChD,IAAI;AACJ,IAAI,MAAM,uBAAuB,GAAG;AACpC,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;AAC/B,IAAI;AACJ,IAAI,MAAM,kCAAkC,GAAG;AAC/C,QAAQ,OAAO;AACf,YAAY,YAAY,EAAE,EAAE;AAC5B,YAAY,SAAS,EAAE,EAAE;AACzB,YAAY,IAAI,EAAE,EAAE;AACpB,YAAY,SAAS,EAAE,CAAC;AACxB,YAAY,QAAQ,EAAE,EAAE;AACxB,YAAY,QAAQ,EAAE,EAAE;AACxB,SAAS;AACT,IAAI;AACJ,IAAI,MAAM,iBAAiB,GAAG,EAAE;AAChC,IAAI,MAAM,oBAAoB,GAAG,EAAE;AACnC,IAAI,MAAM,gBAAgB,GAAG,EAAE;AAC/B,IAAI,MAAM,WAAW,GAAG,EAAE;AAC1B,IAAI,MAAM,eAAe,GAAG,EAAE;AAC9B,IAAI,MAAM,mBAAmB,GAAG,EAAE;AAClC,IAAI,MAAM,YAAY,GAAG;AACzB,QAAQ,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;AACjC,IAAI;AACJ,IAAI,MAAM,YAAY,GAAG,EAAE;AAC3B,IAAI,MAAM,aAAa,GAAG;AAC1B,QAAQ,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE;AAChC,IAAI;AACJ,IAAI,MAAM,cAAc,GAAG,EAAE;AAC7B;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,62 @@
1
+ var capacitorPlayGames = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const PlayGames = core.registerPlugin("PlayGames", {
5
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.PlayGamesWeb()),
6
+ });
7
+
8
+ /**
9
+ * Web / non-native fallback: every method resolves to a safe default (signed
10
+ * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.
11
+ */
12
+ class PlayGamesWeb extends core.WebPlugin {
13
+ async initialize() { }
14
+ async signIn() {
15
+ return { signedIn: false };
16
+ }
17
+ async isSignedIn() {
18
+ return { signedIn: false };
19
+ }
20
+ async getPlayer() {
21
+ return { playerId: "", displayName: "" };
22
+ }
23
+ async requestServerSideAccess() {
24
+ return { authCode: "" };
25
+ }
26
+ async fetchIdentityVerificationSignature() {
27
+ return {
28
+ publicKeyUrl: "",
29
+ signature: "",
30
+ salt: "",
31
+ timestamp: 0,
32
+ playerId: "",
33
+ bundleId: "",
34
+ };
35
+ }
36
+ async unlockAchievement() { }
37
+ async incrementAchievement() { }
38
+ async showAchievements() { }
39
+ async submitScore() { }
40
+ async showLeaderboard() { }
41
+ async showAllLeaderboards() { }
42
+ async loadSnapshot() {
43
+ return { snapshot: null };
44
+ }
45
+ async saveSnapshot() { }
46
+ async listSnapshots() {
47
+ return { snapshots: [] };
48
+ }
49
+ async deleteSnapshot() { }
50
+ }
51
+
52
+ var web = /*#__PURE__*/Object.freeze({
53
+ __proto__: null,
54
+ PlayGamesWeb: PlayGamesWeb
55
+ });
56
+
57
+ exports.PlayGames = PlayGames;
58
+
59
+ return exports;
60
+
61
+ })({}, capacitorExports);
62
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nconst PlayGames = registerPlugin(\"PlayGames\", {\n web: () => import(\"./web\").then((m) => new m.PlayGamesWeb()),\n});\nexport * from \"./definitions\";\nexport { PlayGames };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from \"@capacitor/core\";\n/**\n * Web / non-native fallback: every method resolves to a safe default (signed\n * out, empty). Gate feature usage behind `isSignedIn()`, which is `false` here.\n */\nexport class PlayGamesWeb extends WebPlugin {\n async initialize() { }\n async signIn() {\n return { signedIn: false };\n }\n async isSignedIn() {\n return { signedIn: false };\n }\n async getPlayer() {\n return { playerId: \"\", displayName: \"\" };\n }\n async requestServerSideAccess() {\n return { authCode: \"\" };\n }\n async fetchIdentityVerificationSignature() {\n return {\n publicKeyUrl: \"\",\n signature: \"\",\n salt: \"\",\n timestamp: 0,\n playerId: \"\",\n bundleId: \"\",\n };\n }\n async unlockAchievement() { }\n async incrementAchievement() { }\n async showAchievements() { }\n async submitScore() { }\n async showLeaderboard() { }\n async showAllLeaderboards() { }\n async loadSnapshot() {\n return { snapshot: null };\n }\n async saveSnapshot() { }\n async listSnapshots() {\n return { snapshots: [] };\n }\n async deleteSnapshot() { }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,SAAS,GAAGA,mBAAc,CAAC,WAAW,EAAE;IAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC;IAChE,CAAC;;ICFD;IACA;IACA;IACA;IACO,MAAM,YAAY,SAASC,cAAS,CAAC;IAC5C,IAAI,MAAM,UAAU,GAAG,EAAE;IACzB,IAAI,MAAM,MAAM,GAAG;IACnB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;IAClC,IAAI;IACJ,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;IAClC,IAAI;IACJ,IAAI,MAAM,SAAS,GAAG;IACtB,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;IAChD,IAAI;IACJ,IAAI,MAAM,uBAAuB,GAAG;IACpC,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC/B,IAAI;IACJ,IAAI,MAAM,kCAAkC,GAAG;IAC/C,QAAQ,OAAO;IACf,YAAY,YAAY,EAAE,EAAE;IAC5B,YAAY,SAAS,EAAE,EAAE;IACzB,YAAY,IAAI,EAAE,EAAE;IACpB,YAAY,SAAS,EAAE,CAAC;IACxB,YAAY,QAAQ,EAAE,EAAE;IACxB,YAAY,QAAQ,EAAE,EAAE;IACxB,SAAS;IACT,IAAI;IACJ,IAAI,MAAM,iBAAiB,GAAG,EAAE;IAChC,IAAI,MAAM,oBAAoB,GAAG,EAAE;IACnC,IAAI,MAAM,gBAAgB,GAAG,EAAE;IAC/B,IAAI,MAAM,WAAW,GAAG,EAAE;IAC1B,IAAI,MAAM,eAAe,GAAG,EAAE;IAC9B,IAAI,MAAM,mBAAmB,GAAG,EAAE;IAClC,IAAI,MAAM,YAAY,GAAG;IACzB,QAAQ,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;IACjC,IAAI;IACJ,IAAI,MAAM,YAAY,GAAG,EAAE;IAC3B,IAAI,MAAM,aAAa,GAAG;IAC1B,QAAQ,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE;IAChC,IAAI;IACJ,IAAI,MAAM,cAAc,GAAG,EAAE;IAC7B;;;;;;;;;;;"}
@@ -0,0 +1,73 @@
1
+ import Capacitor
2
+ import GameKit
3
+ import UIKit
4
+
5
+ /// GameKit has no increment API (report overwrites percentComplete), so
6
+ /// increment() loads current progress, adds the steps (percentage points),
7
+ /// then reports the new total.
8
+ internal class AchievementsModule: PgsModule, GKGameCenterControllerDelegate {
9
+ func unlock(_ call: CAPPluginCall) {
10
+ guard let id = call.getString("id") else {
11
+ call.reject("missing id"); return
12
+ }
13
+ let achievement = GKAchievement(identifier: id)
14
+ achievement.percentComplete = 100
15
+ achievement.showsCompletionBanner = true
16
+ GKAchievement.report([achievement]) { error in
17
+ if let error = error {
18
+ call.reject(error.localizedDescription, nil, error)
19
+ } else {
20
+ call.resolve()
21
+ }
22
+ }
23
+ }
24
+
25
+ func increment(_ call: CAPPluginCall) {
26
+ guard let id = call.getString("id") else {
27
+ call.reject("missing id"); return
28
+ }
29
+ let steps = call.getDouble("steps") ?? 0
30
+
31
+ GKAchievement.loadAchievements { existing, error in
32
+ if let error = error {
33
+ call.reject(error.localizedDescription, nil, error)
34
+ return
35
+ }
36
+ let current = existing?.first(where: { $0.identifier == id })
37
+ let achievement = current ?? GKAchievement(identifier: id)
38
+ let next = min(100, (achievement.percentComplete) + steps)
39
+ achievement.percentComplete = next
40
+ achievement.showsCompletionBanner = next >= 100
41
+ GKAchievement.report([achievement]) { reportError in
42
+ if let reportError = reportError {
43
+ call.reject(reportError.localizedDescription, nil, reportError)
44
+ } else {
45
+ call.resolve()
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ func show(_ call: CAPPluginCall) {
52
+ DispatchQueue.main.async {
53
+ guard let presenter = self.topViewController() else {
54
+ call.reject("no presenter available"); return
55
+ }
56
+ let vc: GKGameCenterViewController
57
+ if #available(iOS 14.0, *) {
58
+ vc = GKGameCenterViewController(state: .achievements)
59
+ } else {
60
+ vc = GKGameCenterViewController()
61
+ vc.viewState = .achievements
62
+ }
63
+ vc.gameCenterDelegate = self
64
+ presenter.present(vc, animated: true) { call.resolve() }
65
+ }
66
+ }
67
+
68
+ // MARK: - GKGameCenterControllerDelegate
69
+
70
+ func gameCenterViewControllerDidFinish(_ controller: GKGameCenterViewController) {
71
+ controller.dismiss(animated: true)
72
+ }
73
+ }
@@ -0,0 +1,75 @@
1
+ import Capacitor
2
+ import GameKit
3
+ import UIKit
4
+
5
+ /// GameKit leaderboards: score submission + the native leaderboard overlay.
6
+ internal class LeaderboardsModule: PgsModule, GKGameCenterControllerDelegate {
7
+ func submit(_ call: CAPPluginCall) {
8
+ guard let leaderboardID = call.getString("leaderboardId") else {
9
+ call.reject("missing leaderboardId"); return
10
+ }
11
+ let scoreDouble = call.getDouble("score") ?? 0
12
+ let score = Int(scoreDouble)
13
+
14
+ GKLeaderboard.submitScore(
15
+ score,
16
+ context: 0,
17
+ player: GKLocalPlayer.local,
18
+ leaderboardIDs: [leaderboardID]
19
+ ) { error in
20
+ if let error = error {
21
+ call.reject(error.localizedDescription, nil, error)
22
+ } else {
23
+ call.resolve()
24
+ }
25
+ }
26
+ }
27
+
28
+ func show(_ call: CAPPluginCall) {
29
+ guard let leaderboardID = call.getString("leaderboardId") else {
30
+ call.reject("missing leaderboardId"); return
31
+ }
32
+ DispatchQueue.main.async {
33
+ guard let presenter = self.topViewController() else {
34
+ call.reject("no presenter available"); return
35
+ }
36
+ let vc: GKGameCenterViewController
37
+ if #available(iOS 14.0, *) {
38
+ vc = GKGameCenterViewController(
39
+ leaderboardID: leaderboardID,
40
+ playerScope: .global,
41
+ timeScope: .allTime
42
+ )
43
+ } else {
44
+ vc = GKGameCenterViewController()
45
+ vc.viewState = .leaderboards
46
+ vc.leaderboardIdentifier = leaderboardID
47
+ }
48
+ vc.gameCenterDelegate = self
49
+ presenter.present(vc, animated: true) { call.resolve() }
50
+ }
51
+ }
52
+
53
+ func showAll(_ call: CAPPluginCall) {
54
+ DispatchQueue.main.async {
55
+ guard let presenter = self.topViewController() else {
56
+ call.reject("no presenter available"); return
57
+ }
58
+ let vc: GKGameCenterViewController
59
+ if #available(iOS 14.0, *) {
60
+ vc = GKGameCenterViewController(state: .leaderboards)
61
+ } else {
62
+ vc = GKGameCenterViewController()
63
+ vc.viewState = .leaderboards
64
+ }
65
+ vc.gameCenterDelegate = self
66
+ presenter.present(vc, animated: true) { call.resolve() }
67
+ }
68
+ }
69
+
70
+ // MARK: - GKGameCenterControllerDelegate
71
+
72
+ func gameCenterViewControllerDidFinish(_ controller: GKGameCenterViewController) {
73
+ controller.dismiss(animated: true)
74
+ }
75
+ }
@@ -0,0 +1,40 @@
1
+ import Capacitor
2
+ import Foundation
3
+ import GameKit
4
+ import UIKit
5
+
6
+ /// Shared base for per-feature modules. Inherits `NSObject` so subclasses can
7
+ /// adopt @objc protocols (e.g. `GKGameCenterControllerDelegate`).
8
+ internal class PgsModule: NSObject {
9
+ unowned let plugin: PlayGamesPlugin
10
+ init(_ plugin: PlayGamesPlugin) {
11
+ self.plugin = plugin
12
+ super.init()
13
+ }
14
+
15
+ /// Top-most presented view controller. Callers must be on the main queue.
16
+ func topViewController() -> UIViewController? {
17
+ guard let scene = UIApplication.shared.connectedScenes
18
+ .compactMap({ $0 as? UIWindowScene })
19
+ .first(where: { $0.activationState == .foregroundActive })
20
+ ?? UIApplication.shared.connectedScenes
21
+ .compactMap({ $0 as? UIWindowScene }).first,
22
+ let rootVC = scene.windows.first(where: { $0.isKeyWindow })?.rootViewController
23
+ ?? scene.windows.first?.rootViewController
24
+ else { return nil }
25
+
26
+ var top = rootVC
27
+ while let presented = top.presentedViewController { top = presented }
28
+ return top
29
+ }
30
+ }
31
+
32
+ /// Serialise a GameKit player as the JS-side `PlayerInfo`. Uses the stable
33
+ /// cross-device `gamePlayerID` (not the per-team `teamPlayerID`).
34
+ internal func playerInfo(from player: GKLocalPlayer) -> [String: Any] {
35
+ let info: [String: Any] = [
36
+ "playerId": player.gamePlayerID,
37
+ "displayName": player.displayName,
38
+ ]
39
+ return info
40
+ }
@@ -0,0 +1,74 @@
1
+ import Capacitor
2
+ import Foundation
3
+ import GameKit
4
+
5
+ /// Capacitor 8 plugin wrapping Apple GameKit (sign-in, achievements,
6
+ /// leaderboards, saved games). Thin dispatcher to four per-feature modules.
7
+ @objc(PlayGamesPlugin)
8
+ public class PlayGamesPlugin: CAPPlugin, CAPBridgedPlugin {
9
+ // CAPBridgedPlugin self-registers the plugin on iOS (Capacitor 8). `jsName`
10
+ // must match registerPlugin("PlayGames"); addListener/removeAllListeners
11
+ // come from CAPPlugin and aren't listed here.
12
+ public let identifier = "PlayGamesPlugin"
13
+ public let jsName = "PlayGames"
14
+ public let pluginMethods: [CAPPluginMethod] = [
15
+ CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise),
16
+ CAPPluginMethod(name: "signIn", returnType: CAPPluginReturnPromise),
17
+ CAPPluginMethod(name: "isSignedIn", returnType: CAPPluginReturnPromise),
18
+ CAPPluginMethod(name: "getPlayer", returnType: CAPPluginReturnPromise),
19
+ CAPPluginMethod(name: "fetchIdentityVerificationSignature", returnType: CAPPluginReturnPromise),
20
+ CAPPluginMethod(name: "unlockAchievement", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "incrementAchievement", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "showAchievements", returnType: CAPPluginReturnPromise),
23
+ CAPPluginMethod(name: "submitScore", returnType: CAPPluginReturnPromise),
24
+ CAPPluginMethod(name: "showLeaderboard", returnType: CAPPluginReturnPromise),
25
+ CAPPluginMethod(name: "showAllLeaderboards", returnType: CAPPluginReturnPromise),
26
+ CAPPluginMethod(name: "loadSnapshot", returnType: CAPPluginReturnPromise),
27
+ CAPPluginMethod(name: "saveSnapshot", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "listSnapshots", returnType: CAPPluginReturnPromise),
29
+ CAPPluginMethod(name: "deleteSnapshot", returnType: CAPPluginReturnPromise),
30
+ ]
31
+
32
+ private lazy var signInModule = SignInModule(self)
33
+ private lazy var achievementsModule = AchievementsModule(self)
34
+ private lazy var leaderboardsModule = LeaderboardsModule(self)
35
+ private lazy var savedGamesModule = SavedGamesModule(self)
36
+
37
+ // The GameKit authenticateHandler is installed in initialize(), NOT load():
38
+ // see initialize() for the ATT-ordering reason.
39
+
40
+ /// Expose listener emission to the modules.
41
+ internal func emit(event: String, data: [String: Any]) {
42
+ notifyListeners(event, data: data)
43
+ }
44
+
45
+ // MARK: - JS-callable surface
46
+
47
+ @objc func initialize(_ call: CAPPluginCall) {
48
+ // Setting the handler can auto-present GameKit's "Welcome back" banner,
49
+ // which suppresses an ATT prompt shown while it's on screen, so call
50
+ // initialize() only after the app has requested ATT.
51
+ signInModule.installAuthHandlerIfNeeded()
52
+ call.resolve()
53
+ }
54
+
55
+ @objc func signIn(_ call: CAPPluginCall) { signInModule.signIn(call) }
56
+ @objc func isSignedIn(_ call: CAPPluginCall) { signInModule.isSignedIn(call) }
57
+ @objc func getPlayer(_ call: CAPPluginCall) { signInModule.getPlayer(call) }
58
+ @objc func fetchIdentityVerificationSignature(_ call: CAPPluginCall) {
59
+ signInModule.fetchIdentityVerificationSignature(call)
60
+ }
61
+
62
+ @objc func unlockAchievement(_ call: CAPPluginCall) { achievementsModule.unlock(call) }
63
+ @objc func incrementAchievement(_ call: CAPPluginCall) { achievementsModule.increment(call) }
64
+ @objc func showAchievements(_ call: CAPPluginCall) { achievementsModule.show(call) }
65
+
66
+ @objc func submitScore(_ call: CAPPluginCall) { leaderboardsModule.submit(call) }
67
+ @objc func showLeaderboard(_ call: CAPPluginCall) { leaderboardsModule.show(call) }
68
+ @objc func showAllLeaderboards(_ call: CAPPluginCall) { leaderboardsModule.showAll(call) }
69
+
70
+ @objc func loadSnapshot(_ call: CAPPluginCall) { savedGamesModule.load(call) }
71
+ @objc func saveSnapshot(_ call: CAPPluginCall) { savedGamesModule.save(call) }
72
+ @objc func listSnapshots(_ call: CAPPluginCall) { savedGamesModule.list(call) }
73
+ @objc func deleteSnapshot(_ call: CAPPluginCall) { savedGamesModule.delete(call) }
74
+ }
@@ -0,0 +1,102 @@
1
+ import Capacitor
2
+ import GameKit
3
+
4
+ /// GameKit Saved Games: load/save/list/delete by stable name. Conflicts resolve
5
+ /// most-recently-modified-wins. Requires Game Center auth; otherwise GKLocalPlayer
6
+ /// fails with `GKErrorNotAuthenticated`, forwarded to the caller.
7
+ internal class SavedGamesModule: PgsModule {
8
+ private var player: GKLocalPlayer { GKLocalPlayer.local }
9
+
10
+ func load(_ call: CAPPluginCall) {
11
+ guard let name = call.getString("name") else {
12
+ call.reject("missing name"); return
13
+ }
14
+ player.fetchSavedGames { saved, error in
15
+ if let error = error {
16
+ call.reject(error.localizedDescription, nil, error)
17
+ return
18
+ }
19
+ let matches = (saved ?? []).filter { $0.name == name }
20
+ guard let pick = matches.max(by: { ($0.modificationDate ?? .distantPast) < ($1.modificationDate ?? .distantPast) }) else {
21
+ call.resolve(["snapshot": NSNull()])
22
+ return
23
+ }
24
+ if matches.count > 1 {
25
+ self.player.resolveConflictingSavedGames(matches, with: Data()) { _, _ in }
26
+ }
27
+ pick.loadData { data, dataError in
28
+ if let dataError = dataError {
29
+ call.reject(dataError.localizedDescription, nil, dataError)
30
+ return
31
+ }
32
+ let bytes = data ?? Data()
33
+ let payload: [String: Any] = [
34
+ "name": pick.name ?? name,
35
+ // GKSavedGame has no description field; empty for API parity.
36
+ "description": "",
37
+ "modifiedAt": Int((pick.modificationDate ?? Date()).timeIntervalSince1970 * 1000),
38
+ "data": String(data: bytes, encoding: .utf8) ?? "",
39
+ ]
40
+ call.resolve(["snapshot": payload])
41
+ }
42
+ }
43
+ }
44
+
45
+ func save(_ call: CAPPluginCall) {
46
+ guard let name = call.getString("name") else {
47
+ call.reject("missing name"); return
48
+ }
49
+ guard let data = call.getString("data") else {
50
+ call.reject("missing data"); return
51
+ }
52
+ let bytes = Data(data.utf8)
53
+ player.saveGameData(bytes, withName: name) { _, error in
54
+ if let error = error {
55
+ call.reject(error.localizedDescription, nil, error)
56
+ } else {
57
+ call.resolve()
58
+ }
59
+ }
60
+ }
61
+
62
+ func list(_ call: CAPPluginCall) {
63
+ player.fetchSavedGames { saved, error in
64
+ if let error = error {
65
+ call.reject(error.localizedDescription, nil, error)
66
+ return
67
+ }
68
+ // Collapse cross-device conflicts to one entry per name.
69
+ var byName: [String: GKSavedGame] = [:]
70
+ for s in saved ?? [] {
71
+ let key = s.name ?? ""
72
+ if key.isEmpty { continue }
73
+ if let existing = byName[key],
74
+ (existing.modificationDate ?? .distantPast) >= (s.modificationDate ?? .distantPast) {
75
+ continue
76
+ }
77
+ byName[key] = s
78
+ }
79
+ let snapshots = byName.values.map { s -> [String: Any] in
80
+ [
81
+ "name": s.name ?? "",
82
+ "description": "",
83
+ "modifiedAt": Int((s.modificationDate ?? Date()).timeIntervalSince1970 * 1000),
84
+ ]
85
+ }
86
+ call.resolve(["snapshots": snapshots])
87
+ }
88
+ }
89
+
90
+ func delete(_ call: CAPPluginCall) {
91
+ guard let name = call.getString("name") else {
92
+ call.reject("missing name"); return
93
+ }
94
+ player.deleteSavedGames(withName: name) { error in
95
+ if let error = error {
96
+ call.reject(error.localizedDescription, nil, error)
97
+ } else {
98
+ call.resolve()
99
+ }
100
+ }
101
+ }
102
+ }