@atproto/oauth-client-expo 0.0.1

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 (81) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +140 -0
  4. package/android/.editorconfig +2 -0
  5. package/android/build.gradle +47 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/expo/modules/atprotooauthclient/Crypto.kt +69 -0
  8. package/android/src/main/java/expo/modules/atprotooauthclient/ExpoAtprotoOAuthClientModule.kt +41 -0
  9. package/android/src/main/java/expo/modules/atprotooauthclient/Jose.kt +116 -0
  10. package/android/src/main/java/expo/modules/atprotooauthclient/Records.kt +61 -0
  11. package/dist/ExpoAtprotoOAuthClientModule.d.ts +22 -0
  12. package/dist/ExpoAtprotoOAuthClientModule.d.ts.map +1 -0
  13. package/dist/ExpoAtprotoOAuthClientModule.js +3 -0
  14. package/dist/ExpoAtprotoOAuthClientModule.js.map +1 -0
  15. package/dist/ExpoAtprotoOAuthClientModule.types.d.ts +2 -0
  16. package/dist/ExpoAtprotoOAuthClientModule.types.d.ts.map +1 -0
  17. package/dist/ExpoAtprotoOAuthClientModule.types.js +2 -0
  18. package/dist/ExpoAtprotoOAuthClientModule.types.js.map +1 -0
  19. package/dist/expo-oauth-client-interface.d.ts +6 -0
  20. package/dist/expo-oauth-client-interface.d.ts.map +1 -0
  21. package/dist/expo-oauth-client-interface.js +2 -0
  22. package/dist/expo-oauth-client-interface.js.map +1 -0
  23. package/dist/expo-oauth-client-options.d.ts +9 -0
  24. package/dist/expo-oauth-client-options.d.ts.map +1 -0
  25. package/dist/expo-oauth-client-options.js +2 -0
  26. package/dist/expo-oauth-client-options.js.map +1 -0
  27. package/dist/expo-oauth-client.native.d.ts +13 -0
  28. package/dist/expo-oauth-client.native.d.ts.map +1 -0
  29. package/dist/expo-oauth-client.native.js +130 -0
  30. package/dist/expo-oauth-client.native.js.map +1 -0
  31. package/dist/expo-oauth-client.web.d.ts +9 -0
  32. package/dist/expo-oauth-client.web.d.ts.map +1 -0
  33. package/dist/expo-oauth-client.web.js +24 -0
  34. package/dist/expo-oauth-client.web.js.map +1 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +2 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/polyfill.d.ts +5 -0
  40. package/dist/polyfill.d.ts.map +1 -0
  41. package/dist/polyfill.js +5 -0
  42. package/dist/polyfill.js.map +1 -0
  43. package/dist/utils/expo-key.d.ts +11 -0
  44. package/dist/utils/expo-key.d.ts.map +1 -0
  45. package/dist/utils/expo-key.js +29 -0
  46. package/dist/utils/expo-key.js.map +1 -0
  47. package/dist/utils/mmkv-simple-store-ttl.d.ts +24 -0
  48. package/dist/utils/mmkv-simple-store-ttl.d.ts.map +1 -0
  49. package/dist/utils/mmkv-simple-store-ttl.js +62 -0
  50. package/dist/utils/mmkv-simple-store-ttl.js.map +1 -0
  51. package/dist/utils/mmkv-simple-store.d.ts +18 -0
  52. package/dist/utils/mmkv-simple-store.d.ts.map +1 -0
  53. package/dist/utils/mmkv-simple-store.js +31 -0
  54. package/dist/utils/mmkv-simple-store.js.map +1 -0
  55. package/dist/utils/stores.d.ts +24 -0
  56. package/dist/utils/stores.d.ts.map +1 -0
  57. package/dist/utils/stores.js +99 -0
  58. package/dist/utils/stores.js.map +1 -0
  59. package/expo-module.config.json +9 -0
  60. package/ios/Crypto.swift +83 -0
  61. package/ios/ExpoAtprotoOAuthClient.podspec +31 -0
  62. package/ios/ExpoAtprotoOAuthClientModule.swift +45 -0
  63. package/ios/Jose.swift +137 -0
  64. package/ios/Records.swift +58 -0
  65. package/package.json +52 -0
  66. package/src/ExpoAtprotoOAuthClientModule.ts +33 -0
  67. package/src/ExpoAtprotoOAuthClientModule.types.ts +2 -0
  68. package/src/expo-oauth-client-interface.ts +10 -0
  69. package/src/expo-oauth-client-options.ts +27 -0
  70. package/src/expo-oauth-client.d.ts +6 -0
  71. package/src/expo-oauth-client.native.ts +111 -0
  72. package/src/expo-oauth-client.web.ts +42 -0
  73. package/src/index.ts +4 -0
  74. package/src/polyfill.ts +4 -0
  75. package/src/utils/expo-key.ts +50 -0
  76. package/src/utils/mmkv-simple-store-ttl.ts +90 -0
  77. package/src/utils/mmkv-simple-store.ts +48 -0
  78. package/src/utils/stores.ts +115 -0
  79. package/tsconfig.build.json +8 -0
  80. package/tsconfig.build.tsbuildinfo +1 -0
  81. package/tsconfig.json +4 -0
@@ -0,0 +1,50 @@
1
+ import {
2
+ type Jwk,
3
+ type JwtHeader,
4
+ type JwtPayload,
5
+ Key,
6
+ type SignedJwt,
7
+ type VerifyOptions,
8
+ type VerifyResult,
9
+ } from '@atproto/oauth-client'
10
+ import type { NativeJwk } from '../ExpoAtprotoOAuthClientModule'
11
+ import { default as NativeModule } from '../ExpoAtprotoOAuthClientModule'
12
+
13
+ export type ExpoJwk = Jwk & NativeJwk & { key_ops: ['sign'] }
14
+ export class ExpoKey extends Key<ExpoJwk> {
15
+ async createJwt(header: JwtHeader, payload: JwtPayload): Promise<SignedJwt> {
16
+ return NativeModule.createJwt(
17
+ JSON.stringify(header),
18
+ JSON.stringify(payload),
19
+ toNativeJwk(this.jwk),
20
+ )
21
+ }
22
+
23
+ async verifyJwt<C extends string = never>(
24
+ token: SignedJwt,
25
+ options: VerifyOptions<C> = {},
26
+ ): Promise<VerifyResult<C>> {
27
+ return NativeModule.verifyJwt(token, toNativeJwk(this.jwk), options)
28
+ }
29
+
30
+ static async generate(algs: string[]): Promise<ExpoKey> {
31
+ if (algs.includes('ES256')) {
32
+ const jwk = await NativeModule.generatePrivateJwk('ES256')
33
+ return new ExpoKey({ ...jwk, key_ops: ['sign'] })
34
+ }
35
+
36
+ throw TypeError(`No supported algorithm found in: ${algs.join(', ')}`)
37
+ }
38
+ }
39
+
40
+ function toNativeJwk(jwk: ExpoJwk): NativeJwk {
41
+ return {
42
+ kty: jwk.kty,
43
+ crv: jwk.crv,
44
+ kid: jwk.kid,
45
+ x: jwk.x,
46
+ y: jwk.y,
47
+ d: jwk.d,
48
+ alg: jwk.alg,
49
+ }
50
+ }
@@ -0,0 +1,90 @@
1
+ import { Configuration, MMKV } from 'react-native-mmkv'
2
+ import type { SimpleStore, Value } from '@atproto-labs/simple-store'
3
+ import { MMKVSimpleStore, MMKVSimpleStoreOptions } from './mmkv-simple-store'
4
+
5
+ export type MMKVSimpleStoreTTLOptions<V extends Value> =
6
+ MMKVSimpleStoreOptions<V> & {
7
+ clearInterval?: null | false | number
8
+ expiresAt: (value: V) => null | number
9
+ }
10
+
11
+ /**
12
+ * A {@link SimpleStore} implementation based on {@link MMKVSimpleStore} that
13
+ * supports expiring entries after a certain time.
14
+ */
15
+ export class MMKVSimpleStoreTTL<V extends Value>
16
+ extends MMKVSimpleStore<V>
17
+ implements Disposable, SimpleStore<string, V>
18
+ {
19
+ readonly #store: MMKV
20
+ readonly #expiresAt: (value: V) => null | number
21
+ readonly #clearTimer?: ReturnType<typeof setInterval>
22
+
23
+ constructor({
24
+ clearInterval = 60 * 1e3,
25
+ expiresAt,
26
+ encode,
27
+ decode,
28
+
29
+ ...config
30
+ }: MMKVSimpleStoreTTLOptions<V> & Configuration) {
31
+ super({ ...config, encode, decode })
32
+
33
+ this.#store = new MMKV({ ...config, id: `${config.id}.exp` })
34
+ this.#expiresAt = expiresAt
35
+ if (clearInterval) {
36
+ this.#clearTimer = setInterval(() => this.clearExpired(), clearInterval)
37
+ }
38
+
39
+ this.clearExpired()
40
+ }
41
+
42
+ [Symbol.dispose]() {
43
+ clearInterval(this.#clearTimer)
44
+ this.clearExpired()
45
+ }
46
+
47
+ override set(key: string, value: V): void {
48
+ super.set(key, value)
49
+
50
+ const expirationDate = this.#expiresAt.call(null, value)
51
+ if (expirationDate == null) this.#store.delete(key)
52
+ else this.#store.set(key, expirationDate)
53
+ }
54
+
55
+ override get(key: string): V | undefined {
56
+ if (this.isExpired(key)) {
57
+ this.del(key)
58
+ return undefined
59
+ }
60
+
61
+ return super.get(key)
62
+ }
63
+
64
+ override del(key: string): void {
65
+ super.del(key)
66
+ this.#store.delete(key)
67
+ }
68
+
69
+ override clear(): void {
70
+ super.clear()
71
+ this.#store.clearAll()
72
+ }
73
+
74
+ getExpirationTime(key: string): number | undefined {
75
+ return this.#store.getNumber(key) ?? undefined
76
+ }
77
+
78
+ isExpired(key: string): boolean {
79
+ const expirationTime = this.getExpirationTime(key)
80
+ return expirationTime != null && expirationTime < Date.now()
81
+ }
82
+
83
+ clearExpired() {
84
+ for (const key of this.#store.getAllKeys() ?? []) {
85
+ if (this.isExpired(key)) {
86
+ this.del(key)
87
+ }
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,48 @@
1
+ import { Configuration, MMKV } from 'react-native-mmkv'
2
+ import type { SimpleStore, Value } from '@atproto-labs/simple-store'
3
+
4
+ export type MMKVSimpleStoreOptions<V extends Value> = {
5
+ decode: (value: string) => V
6
+ encode: (value: V) => string
7
+ }
8
+
9
+ /**
10
+ * A {@link SimpleStore} implementation using {@link MMKV} for storage.
11
+ */
12
+ export class MMKVSimpleStore<V extends Value>
13
+ implements SimpleStore<string, V>
14
+ {
15
+ readonly #store: MMKV
16
+ readonly #encode: (value: V) => string
17
+ readonly #decode: (value: string) => V
18
+
19
+ constructor({
20
+ decode,
21
+ encode,
22
+ ...config
23
+ }: MMKVSimpleStoreOptions<V> & Configuration) {
24
+ this.#store = new MMKV(config)
25
+ this.#decode = decode
26
+ this.#encode = encode
27
+ }
28
+
29
+ set(key: string, value: V): void {
30
+ const encoded = this.#encode.call(null, value)
31
+ this.#store.set(key, encoded)
32
+ }
33
+
34
+ get(key: string): V | undefined {
35
+ const value = this.#store.getString(key)
36
+ if (value === undefined) return undefined
37
+
38
+ return this.#decode.call(null, value)
39
+ }
40
+
41
+ del(key: string): void {
42
+ this.#store.delete(key)
43
+ }
44
+
45
+ clear() {
46
+ this.#store.clearAll()
47
+ }
48
+ }
@@ -0,0 +1,115 @@
1
+ import type {
2
+ DidDocument,
3
+ InternalStateData,
4
+ OAuthAuthorizationServerMetadata,
5
+ OAuthProtectedResourceMetadata,
6
+ ResolvedHandle,
7
+ Session,
8
+ } from '@atproto/oauth-client'
9
+ import { ExpoKey } from './expo-key'
10
+ import { MMKVSimpleStoreTTL } from './mmkv-simple-store-ttl'
11
+
12
+ const MMKV_ID = 'expo-atproto-oauth-client'
13
+
14
+ export class AuthorizationServerMetadataCache extends MMKVSimpleStoreTTL<OAuthAuthorizationServerMetadata> {
15
+ constructor() {
16
+ super({
17
+ id: `${MMKV_ID}.authorizationServerMetadata`,
18
+ expiresAt: oneMinuteFromNow,
19
+ decode: JSON.parse,
20
+ encode: JSON.stringify,
21
+ })
22
+ }
23
+ }
24
+
25
+ export class ProtectedResourceMetadataCache extends MMKVSimpleStoreTTL<OAuthProtectedResourceMetadata> {
26
+ constructor() {
27
+ super({
28
+ id: `${MMKV_ID}.protectedResourceMetadata`,
29
+ expiresAt: oneMinuteFromNow,
30
+ decode: JSON.parse,
31
+ encode: JSON.stringify,
32
+ })
33
+ }
34
+ }
35
+
36
+ export class DpopNonceCache extends MMKVSimpleStoreTTL<string> {
37
+ constructor() {
38
+ super({
39
+ id: `${MMKV_ID}.dpopNonce`,
40
+ expiresAt: tenMinutesFromNow,
41
+ decode: identity,
42
+ encode: identity,
43
+ })
44
+ }
45
+ }
46
+
47
+ export class DidCache extends MMKVSimpleStoreTTL<DidDocument> {
48
+ constructor() {
49
+ super({
50
+ id: `${MMKV_ID}.did`,
51
+ expiresAt: oneMinuteFromNow,
52
+ decode: JSON.parse,
53
+ encode: JSON.stringify,
54
+ })
55
+ }
56
+ }
57
+
58
+ export class HandleCache extends MMKVSimpleStoreTTL<ResolvedHandle> {
59
+ constructor() {
60
+ super({
61
+ id: `${MMKV_ID}.handle`,
62
+ expiresAt: oneMinuteFromNow,
63
+ decode: JSON.parse,
64
+ encode: JSON.stringify,
65
+ })
66
+ }
67
+ }
68
+
69
+ export class StateStore extends MMKVSimpleStoreTTL<InternalStateData> {
70
+ constructor() {
71
+ super({
72
+ id: `${MMKV_ID}.state`,
73
+ expiresAt: tenMinutesFromNow,
74
+ decode: (value) => {
75
+ const parsed = JSON.parse(value)
76
+ return { ...parsed, dpopKey: new ExpoKey(parsed.dpopKey) }
77
+ },
78
+ encode: (value) => {
79
+ return JSON.stringify({ ...value, dpopKey: value.dpopKey.jwk })
80
+ },
81
+ })
82
+ }
83
+ }
84
+
85
+ export class SessionStore extends MMKVSimpleStoreTTL<Session> {
86
+ constructor() {
87
+ super({
88
+ id: `${MMKV_ID}.session`,
89
+ expiresAt: ({ tokenSet }) => {
90
+ if (tokenSet.refresh_token) return null
91
+ if (tokenSet.expires_at) return new Date(tokenSet.expires_at).valueOf()
92
+ return null
93
+ },
94
+ decode: (value) => {
95
+ const parsed = JSON.parse(value)
96
+ return { ...parsed, dpopKey: new ExpoKey(parsed.dpopKey) }
97
+ },
98
+ encode: (value) => {
99
+ return JSON.stringify({ ...value, dpopKey: value.dpopKey.jwk })
100
+ },
101
+ })
102
+ }
103
+ }
104
+
105
+ function identity<T>(x: T): T {
106
+ return x
107
+ }
108
+
109
+ function tenMinutesFromNow() {
110
+ return Date.now() + 10 * 60e3
111
+ }
112
+
113
+ function oneMinuteFromNow() {
114
+ return Date.now() + 60e3
115
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": ["../../../tsconfig/expo.json"],
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["./src"]
8
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/ExpoAtprotoOAuthClientModule.ts","./src/ExpoAtprotoOAuthClientModule.types.ts","./src/expo-oauth-client-interface.ts","./src/expo-oauth-client-options.ts","./src/expo-oauth-client.d.ts","./src/expo-oauth-client.native.ts","./src/expo-oauth-client.web.ts","./src/index.ts","./src/polyfill.ts","./src/utils/expo-key.ts","./src/utils/mmkv-simple-store-ttl.ts","./src/utils/mmkv-simple-store.ts","./src/utils/stores.ts"],"version":"5.8.3"}
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "include": [],
3
+ "references": [{ "path": "./tsconfig.build.json" }]
4
+ }