@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
|
@@ -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"}
|
package/dist/esm/web.js
ADDED
|
@@ -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
|
+
}
|