tangled
alpha
login
or
join now
owdproject.org
/
module-atproto-persistence
1
fork
atom
Pinia Persistent Storage via AT Protocol for Open Web Desktop
1
fork
atom
overview
issues
pulls
pipelines
chrome: Fix linting and formatting
dxlliv
10 months ago
53eece10
129aa17e
+210
-103
3 changed files
expand all
collapse all
unified
split
README.md
runtime
plugin.ts
utils
utilAtprotoApplicationStates.ts
+1
README.md
···
12
12
for storing Open Web Desktop states persistently on the AT Protocol.
13
13
14
14
## Features
15
15
+
15
16
- Configures `pinia-plugin-persistedstate-2` to use `atproto`
16
17
- Enables persistent storage for Pinia stores
17
18
- Works seamlessly with Nuxt
+55
-103
runtime/plugin.ts
···
1
1
-
import {createPersistedStatePlugin} from 'pinia-plugin-persistedstate-2'
2
2
-
import {useOnline} from '@vueuse/core'
3
3
-
import {deepEqual} from "@owdproject/core/runtime/utils/utilCommon";
4
4
-
import {defineNuxtPlugin, useNuxtApp} from "nuxt/app"
5
5
-
import {toRaw} from "vue"
6
6
-
import {useAtproto} from "#imports"
7
7
-
import {usePinia} from "#imports"
1
1
+
import { createPersistedStatePlugin } from 'pinia-plugin-persistedstate-2'
2
2
+
import { deepEqual } from '@owdproject/core/runtime/utils/utilCommon'
3
3
+
import { defineNuxtPlugin, useRuntimeConfig } from 'nuxt/app'
4
4
+
import { toRaw } from 'vue'
5
5
+
import { usePinia, useAtproto } from '#imports'
6
6
+
8
7
import localforage from 'localforage/src/localforage.js'
9
8
import {
10
10
-
getAtprotoApplicationState,
11
11
-
putAtprotoApplicationState,
12
12
-
listAtprotoApplicationStateRecords, parseAtprotoStoreKey
13
13
-
} from "./utils/utilAtprotoApplications";
14
14
-
15
15
-
function shouldSyncWithATProto(
16
16
-
piniaStoreKey: string,
17
17
-
atprotoApplicationsRecords?: {
18
18
-
windows: any[],
19
19
-
meta: any[],
20
20
-
}
21
21
-
) {
22
22
-
const online = useOnline();
23
23
-
const {$atproto} = useNuxtApp();
24
24
-
25
25
-
if (!online.value || !$atproto.session.value) return false;
26
26
-
27
27
-
const parsed = parseAtprotoStoreKey(piniaStoreKey);
28
28
-
29
29
-
if (!parsed) return false;
30
30
-
31
31
-
const { collection, rkey } = parsed;
32
32
-
33
33
-
if (collection === 'org.owdproject.application.windows' && atprotoApplicationsRecords?.windows) {
34
34
-
return atprotoApplicationsRecords.windows.some(record => record.uri.endsWith(rkey));
35
35
-
}
36
36
-
37
37
-
if (collection === 'org.owdproject.application.meta' && atprotoApplicationsRecords?.meta) {
38
38
-
return atprotoApplicationsRecords.meta.some(record => record.uri.endsWith(rkey));
39
39
-
}
40
40
-
41
41
-
return true;
42
42
-
}
9
9
+
loadActorDesktop,
10
10
+
putAtprotoApplicationState,
11
11
+
parseAtprotoStoreKey,
12
12
+
} from './utils/utilAtprotoApplicationStates'
43
13
44
14
export default defineNuxtPlugin({
45
45
-
name: 'owd-plugin-pinia-atproto',
46
46
-
dependsOn: ['atproto', 'owd-plugin-atproto'],
47
47
-
async setup() {
48
48
-
const pinia = usePinia()
49
49
-
const atproto = useAtproto()
50
50
-
51
51
-
const atprotoApplicationsRecords = {
52
52
-
windows: atproto.agent.account
53
53
-
? await listAtprotoApplicationStateRecords(atproto.agent.account, 'org.owdproject.application.windows')
54
54
-
: [],
55
55
-
meta: atproto.agent.account
56
56
-
? await listAtprotoApplicationStateRecords(atproto.agent.account, 'org.owdproject.application.meta')
57
57
-
: []
58
58
-
}
59
59
-
60
60
-
pinia.use(
61
61
-
createPersistedStatePlugin({
62
62
-
persist: false,
63
63
-
storage: {
64
64
-
getItem: async (piniaKey) => {
65
65
-
const piniaValue = await localforage.getItem(piniaKey);
66
66
-
const parsed = parseAtprotoStoreKey(piniaKey);
67
67
-
68
68
-
if (!parsed || !shouldSyncWithATProto(piniaKey, atprotoApplicationsRecords)) {
69
69
-
return piniaValue;
70
70
-
}
71
71
-
72
72
-
const { collection, rkey } = parsed;
73
73
-
74
74
-
return getAtprotoApplicationState(atproto.agent.account, collection, rkey)
75
75
-
.then((response) => JSON.stringify(response.data.value))
76
76
-
.catch(() => piniaValue);
77
77
-
},
78
78
-
setItem: async (piniaKey, piniaValue) => {
79
79
-
const previousPiniaValue = await localforage.getItem(piniaKey);
80
80
-
await localforage.setItem(piniaKey, piniaValue);
15
15
+
name: 'owd-plugin-atproto-persistence',
16
16
+
dependsOn: ['owd-plugin-atproto'],
17
17
+
async setup(nuxt) {
18
18
+
const pinia = usePinia()
19
19
+
const atproto = useAtproto()
20
20
+
const runtimeConfig = useRuntimeConfig()
81
21
82
82
-
const parsed = parseAtprotoStoreKey(piniaKey);
22
22
+
if (
23
23
+
runtimeConfig.public.atprotoPersistence &&
24
24
+
runtimeConfig.public.atprotoPersistence.loadOwnerDesktopOnMounted
25
25
+
) {
26
26
+
loadActorDesktop(runtimeConfig.public.atprotoDesktop.owner.did)
27
27
+
}
83
28
84
84
-
if (!parsed) {
85
85
-
return piniaValue;
86
86
-
}
29
29
+
pinia.use(
30
30
+
createPersistedStatePlugin({
31
31
+
persist: false,
32
32
+
storage: {
33
33
+
getItem: async (piniaKey) => {
34
34
+
return localforage.getItem(piniaKey)
35
35
+
},
36
36
+
setItem: async (piniaKey, piniaValue) => {
37
37
+
const previousPiniaValue = await localforage.getItem(piniaKey)
38
38
+
await localforage.setItem(piniaKey, piniaValue)
87
39
88
88
-
const { collection, rkey } = parsed;
40
40
+
const atprotoTargetRecord = parseAtprotoStoreKey(piniaKey)
89
41
90
90
-
if (deepEqual(toRaw(piniaValue), toRaw(previousPiniaValue))) {
91
91
-
return piniaValue;
92
92
-
}
42
42
+
if (!atprotoTargetRecord || !atproto.agent.account) {
43
43
+
return piniaValue
44
44
+
}
93
45
94
94
-
return putAtprotoApplicationState(
95
95
-
atproto.agent.account,
96
96
-
collection,
97
97
-
rkey,
98
98
-
JSON.parse(piniaValue)
99
99
-
);
100
100
-
},
101
101
-
removeItem: async (key) => {
102
102
-
const online = useOnline()
103
103
-
await localforage.removeItem(key)
46
46
+
const { collection, rkey } = atprotoTargetRecord
104
47
105
105
-
if (!key.startsWith('owd/')) {
106
106
-
return
107
107
-
}
48
48
+
if (deepEqual(toRaw(piniaValue), toRaw(previousPiniaValue))) {
49
49
+
return piniaValue
50
50
+
}
108
51
109
109
-
return
110
110
-
},
111
111
-
},
112
112
-
}),
113
113
-
)
114
114
-
}
52
52
+
return putAtprotoApplicationState(
53
53
+
atproto.agent.account,
54
54
+
atproto.agent.account.assertDid,
55
55
+
collection,
56
56
+
rkey,
57
57
+
JSON.parse(piniaValue),
58
58
+
)
59
59
+
},
60
60
+
removeItem: async (key) => {
61
61
+
await localforage.removeItem(key)
62
62
+
},
63
63
+
},
64
64
+
}),
65
65
+
)
66
66
+
},
115
67
})
+154
runtime/utils/utilAtprotoApplicationStates.ts
···
1
1
+
import { useAgent, resolveActorServiceEndpoint } from '#imports'
2
2
+
import { useDesktopStore } from '@owdproject/core/runtime/stores/storeDesktop'
3
3
+
import { useApplicationWindowsStore } from '@owdproject/core/runtime/stores/storeApplicationWindows'
4
4
+
import { useApplicationMetaStore } from '@owdproject/core/runtime/stores/storeApplicationMeta'
5
5
+
6
6
+
export function parseAtprotoStoreKey(
7
7
+
key: string,
8
8
+
): { collection: string; rkey: string } | null {
9
9
+
if (!key.startsWith('owd/')) return null
10
10
+
11
11
+
const parts = key.split('/')
12
12
+
13
13
+
if (parts[1] === 'application') {
14
14
+
const last = parts[parts.length - 1]
15
15
+
if (last === 'windows' || last === 'meta') {
16
16
+
const nome = parts.slice(2, -1).join('/')
17
17
+
return {
18
18
+
collection: `org.owdproject.application.${last}`,
19
19
+
rkey: nome,
20
20
+
}
21
21
+
}
22
22
+
}
23
23
+
24
24
+
// owd/desktop
25
25
+
if (parts[1] === 'desktop') {
26
26
+
const rkey = parts[2] ?? 'self'
27
27
+
return {
28
28
+
collection: 'org.owdproject.desktop',
29
29
+
rkey,
30
30
+
}
31
31
+
}
32
32
+
33
33
+
return null
34
34
+
}
35
35
+
36
36
+
export function listAtprotoApplicationStateRecords(
37
37
+
agent: any,
38
38
+
repo: string,
39
39
+
collection: string,
40
40
+
) {
41
41
+
return agent.com.atproto.repo
42
42
+
.listRecords({
43
43
+
repo,
44
44
+
collection,
45
45
+
})
46
46
+
.then((response: any) => response.data)
47
47
+
.then((data: any) => data.records)
48
48
+
.catch(() => [])
49
49
+
}
50
50
+
51
51
+
export function getAtprotoApplicationState(
52
52
+
agent: any,
53
53
+
repo: string,
54
54
+
collection: string,
55
55
+
rkey: string,
56
56
+
) {
57
57
+
return agent.com.atproto.repo.getRecord({
58
58
+
repo,
59
59
+
collection,
60
60
+
rkey,
61
61
+
})
62
62
+
}
63
63
+
64
64
+
export function putAtprotoApplicationState(
65
65
+
agent: any,
66
66
+
collection: string,
67
67
+
rkey: string,
68
68
+
record: any,
69
69
+
) {
70
70
+
return agent.com.atproto.repo.putRecord({
71
71
+
repo: agent?.assertDid as string,
72
72
+
collection,
73
73
+
rkey,
74
74
+
record,
75
75
+
})
76
76
+
}
77
77
+
78
78
+
/**
79
79
+
* Load actor desktop
80
80
+
*
81
81
+
* @param actorDid
82
82
+
*/
83
83
+
export async function loadActorDesktop(actorDid: string) {
84
84
+
const actorServiceEndpoint = await resolveActorServiceEndpoint(actorDid)
85
85
+
const actorAgent = useAgent(actorServiceEndpoint)
86
86
+
87
87
+
const [
88
88
+
atprotoActorDesktopRecord,
89
89
+
atprotoApplicationWindowsList,
90
90
+
atprotoApplicationMetaList,
91
91
+
] = await Promise.allSettled([
92
92
+
getAtprotoApplicationState(
93
93
+
actorAgent,
94
94
+
actorDid,
95
95
+
'org.owdproject.desktop',
96
96
+
'self',
97
97
+
),
98
98
+
listAtprotoApplicationStateRecords(
99
99
+
actorAgent,
100
100
+
actorDid,
101
101
+
'org.owdproject.application.windows',
102
102
+
),
103
103
+
listAtprotoApplicationStateRecords(
104
104
+
actorAgent,
105
105
+
actorDid,
106
106
+
'org.owdproject.application.meta',
107
107
+
),
108
108
+
])
109
109
+
110
110
+
// load actor applications desktop settings
111
111
+
if (atprotoActorDesktopRecord.status === 'fulfilled') {
112
112
+
// todo validate
113
113
+
114
114
+
if (
115
115
+
atprotoActorDesktopRecord.value &&
116
116
+
atprotoActorDesktopRecord.value.data
117
117
+
) {
118
118
+
useDesktopStore().$patch(atprotoActorDesktopRecord.value.data.value.state)
119
119
+
}
120
120
+
}
121
121
+
122
122
+
let applicationWindowsStore
123
123
+
let applicationMetaStore
124
124
+
125
125
+
// load actor applications windows
126
126
+
if (atprotoApplicationWindowsList.status === 'fulfilled') {
127
127
+
for (const atprotoApplicationWindowRecord of atprotoApplicationWindowsList.value) {
128
128
+
const atprotoApplicationId = atprotoApplicationWindowRecord.uri
129
129
+
.split('/')
130
130
+
.pop()
131
131
+
132
132
+
applicationWindowsStore = useApplicationWindowsStore(atprotoApplicationId)
133
133
+
134
134
+
applicationWindowsStore.$patch({
135
135
+
windows: atprotoApplicationWindowRecord.value.windows,
136
136
+
})
137
137
+
}
138
138
+
}
139
139
+
140
140
+
// load actor applications meta
141
141
+
if (atprotoApplicationMetaList.status === 'fulfilled') {
142
142
+
for (const atprotoApplicationMetaRecord of atprotoApplicationMetaList.value) {
143
143
+
const atprotoApplicationId = atprotoApplicationMetaRecord.uri
144
144
+
.split('/')
145
145
+
.pop()
146
146
+
147
147
+
applicationMetaStore = useApplicationMetaStore(atprotoApplicationId)()
148
148
+
149
149
+
applicationMetaStore.$patch({
150
150
+
...atprotoApplicationMetaRecord.value,
151
151
+
})
152
152
+
}
153
153
+
}
154
154
+
}