@automerge/automerge-repo-react-hooks 0.1.2 → 0.1.4-alpha.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/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { useDocument } from "./useDocument.js";
2
+ export { useBootstrap } from './useBootstrap.js';
2
3
  export { useHandle } from "./useHandle.js";
3
4
  export { RepoContext, useRepo } from "./useRepo.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { useDocument } from "./useDocument.js";
2
+ export { useBootstrap } from './useBootstrap.js';
2
3
  export { useHandle } from "./useHandle.js";
3
4
  export { RepoContext, useRepo } from "./useRepo.js";
@@ -0,0 +1,28 @@
1
+ import { DocHandle, Repo } from "@automerge/automerge-repo";
2
+ export declare const setHash: (hash: string, pushState?: boolean) => void;
3
+ export declare const useHash: () => string;
4
+ /**
5
+ * This hook is used to set up a single document as the base of an app session.
6
+ * This is a common pattern for simple multiplayer apps with shareable URLs.
7
+ *
8
+ * It will first check for the document ID in the URL hash:
9
+ * //myapp/#documentId=[document ID]
10
+ * Failing that, it will check for a `documentId` key in localStorage.
11
+ * Failing that, it will call onNoDocument, expecting a handle to be returned.
12
+ *
13
+ * The URL and localStorage will then be updated.
14
+ * Finally, it will return the document ID.
15
+ *
16
+ * @param {string?} props.key Key to use for the URL hash and localStorage
17
+ * @param {function?} props.fallback Function returning a document handle called if lookup fails. Defaults to repo.create()
18
+ * @param {function?} props.onInvalidDocumentId Function to call if documentId is invalid; signature (error) => (repo, onCreate)
19
+ * @returns {DocHandle} The document handle
20
+ */
21
+ interface UseBootstrapOptions<T> {
22
+ key?: string;
23
+ onNoDocument?: (repo: Repo) => DocHandle<T>;
24
+ onInvalidDocumentId?(repo: Repo, error: Error): DocHandle<T>;
25
+ }
26
+ export declare const useBootstrap: <T>({ key, onNoDocument, onInvalidDocumentId, }?: UseBootstrapOptions<T>) => DocHandle<T>;
27
+ export {};
28
+ //# sourceMappingURL=useBootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBootstrap.d.ts","sourceRoot":"","sources":["../src/useBootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAc,IAAI,EAAE,MAAM,2BAA2B,CAAA;AAKvE,eAAO,MAAM,OAAO,SAAU,MAAM,8BAUnC,CAAA;AAGD,eAAO,MAAM,OAAO,cAQnB,CAAA;AAwBD;;;;;;;;;;;;;;;;GAgBG;AACH,UAAU,mBAAmB,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAA;IAC3C,mBAAmB,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;CAC7D;AAED,eAAO,MAAM,YAAY,2FAgCxB,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { useEffect, useState, useMemo } from "react";
2
+ import { useRepo } from "./useRepo.js";
3
+ // Set URL hash
4
+ export const setHash = (hash, pushState = false) => {
5
+ // Update URL hash
6
+ history[pushState ? "pushState" : "replaceState"]("", "", "#" + hash);
7
+ // Send fake hashchange event
8
+ window.dispatchEvent(new HashChangeEvent("hashchange", {
9
+ newURL: window.location.origin + window.location.pathname + hash,
10
+ oldURL: window.location.href,
11
+ }));
12
+ };
13
+ // Get current URL hash
14
+ export const useHash = () => {
15
+ const [hashValue, setHashValue] = useState(window.location.hash);
16
+ useEffect(() => {
17
+ const handler = () => void setHashValue(window.location.hash);
18
+ window.addEventListener("hashchange", handler);
19
+ return () => void window.removeEventListener("hashchange", handler);
20
+ }, []);
21
+ return hashValue;
22
+ };
23
+ // Get a key from a query-param-style URL hash
24
+ const getQueryParamValue = (key, hash) => new URLSearchParams(hash.substr(1)).get(key);
25
+ const setQueryParamValue = (key, value, hash) => {
26
+ const u = new URLSearchParams(hash.substr(1));
27
+ u.set(key, value);
28
+ return u.toString();
29
+ };
30
+ const getDocumentId = (key, hash) => key && (getQueryParamValue(key, hash) || localStorage.getItem(key));
31
+ const setDocumentId = (key, documentId) => {
32
+ if (key) {
33
+ // Only set URL hash if document ID changed
34
+ if (documentId !== getQueryParamValue(key, window.location.hash))
35
+ setHash(setQueryParamValue(key, documentId, window.location.hash));
36
+ }
37
+ if (key)
38
+ localStorage.setItem(key, documentId);
39
+ };
40
+ export const useBootstrap = ({ key = "documentId", onNoDocument = repo => repo.create(), onInvalidDocumentId, } = {}) => {
41
+ const repo = useRepo();
42
+ const hash = useHash();
43
+ // Try to get existing document; else create a new one
44
+ const handle = useMemo(() => {
45
+ const existingDocumentId = getDocumentId(key, hash);
46
+ try {
47
+ return existingDocumentId
48
+ ? repo.find(existingDocumentId)
49
+ : onNoDocument(repo);
50
+ }
51
+ catch (error) {
52
+ // Presumably the documentId was invalid
53
+ if (existingDocumentId && onInvalidDocumentId)
54
+ return onInvalidDocumentId(repo, error);
55
+ // Forward other errors
56
+ throw error;
57
+ }
58
+ }, [hash, repo, onNoDocument, onInvalidDocumentId]);
59
+ // Update hashroute & localStorage on changes
60
+ useEffect(() => {
61
+ if (handle) {
62
+ setDocumentId(key, handle.documentId);
63
+ }
64
+ }, [hash, handle]);
65
+ return handle;
66
+ };
@@ -8,7 +8,7 @@ export function useDocument(documentId) {
8
8
  if (!handle)
9
9
  return;
10
10
  handle.value().then(v => setDoc(v));
11
- const onPatch = (h) => setDoc(h.after);
11
+ const onPatch = (h) => setDoc(h.patchInfo.after);
12
12
  handle.on("patch", onPatch);
13
13
  const cleanup = () => {
14
14
  handle.removeListener("patch", onPatch);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo-react-hooks",
3
- "version": "0.1.2",
3
+ "version": "0.1.4-alpha.0",
4
4
  "description": "Hooks to access an Automerge Repo from your react app.",
5
5
  "repository": "https://github.com/automerge/automerge-repo",
6
6
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -13,7 +13,7 @@
13
13
  "watch": "npm-watch"
14
14
  },
15
15
  "dependencies": {
16
- "@automerge/automerge-repo": "^0.1.2"
16
+ "@automerge/automerge-repo": "^0.1.4-alpha.0"
17
17
  },
18
18
  "watch": {
19
19
  "build": {
@@ -26,5 +26,5 @@
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
29
- "gitHead": "a6313d0e6de2951c98ecffd4a6f01b82c46613e3"
29
+ "gitHead": "9a431d695250ea3ee2e58b4d60d2fb6a90c904e4"
30
30
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { useDocument } from "./useDocument.js"
2
+ export { useBootstrap } from './useBootstrap.js'
2
3
  export { useHandle } from "./useHandle.js"
3
4
  export { RepoContext, useRepo } from "./useRepo.js"
@@ -0,0 +1,106 @@
1
+ import { DocHandle, DocumentId, Repo } from "@automerge/automerge-repo"
2
+ import { useEffect, useState, useMemo } from "react"
3
+ import { useRepo } from "./useRepo.js"
4
+
5
+ // Set URL hash
6
+ export const setHash = (hash: string, pushState = false) => {
7
+ // Update URL hash
8
+ history[pushState ? "pushState" : "replaceState"]("", "", "#" + hash)
9
+ // Send fake hashchange event
10
+ window.dispatchEvent(
11
+ new HashChangeEvent("hashchange", {
12
+ newURL: window.location.origin + window.location.pathname + hash,
13
+ oldURL: window.location.href,
14
+ })
15
+ )
16
+ }
17
+
18
+ // Get current URL hash
19
+ export const useHash = () => {
20
+ const [hashValue, setHashValue] = useState(window.location.hash)
21
+ useEffect(() => {
22
+ const handler = () => void setHashValue(window.location.hash)
23
+ window.addEventListener("hashchange", handler)
24
+ return () => void window.removeEventListener("hashchange", handler)
25
+ }, [])
26
+ return hashValue
27
+ }
28
+
29
+ // Get a key from a query-param-style URL hash
30
+ const getQueryParamValue = (key: string, hash) =>
31
+ new URLSearchParams(hash.substr(1)).get(key)
32
+
33
+ const setQueryParamValue = (key: string, value, hash): string => {
34
+ const u = new URLSearchParams(hash.substr(1))
35
+ u.set(key, value)
36
+ return u.toString()
37
+ }
38
+
39
+ const getDocumentId = (key, hash) =>
40
+ key && (getQueryParamValue(key, hash) || localStorage.getItem(key))
41
+
42
+ const setDocumentId = (key, documentId) => {
43
+ if (key) {
44
+ // Only set URL hash if document ID changed
45
+ if (documentId !== getQueryParamValue(key, window.location.hash))
46
+ setHash(setQueryParamValue(key, documentId, window.location.hash))
47
+ }
48
+ if (key) localStorage.setItem(key, documentId)
49
+ }
50
+
51
+ /**
52
+ * This hook is used to set up a single document as the base of an app session.
53
+ * This is a common pattern for simple multiplayer apps with shareable URLs.
54
+ *
55
+ * It will first check for the document ID in the URL hash:
56
+ * //myapp/#documentId=[document ID]
57
+ * Failing that, it will check for a `documentId` key in localStorage.
58
+ * Failing that, it will call onNoDocument, expecting a handle to be returned.
59
+ *
60
+ * The URL and localStorage will then be updated.
61
+ * Finally, it will return the document ID.
62
+ *
63
+ * @param {string?} props.key Key to use for the URL hash and localStorage
64
+ * @param {function?} props.fallback Function returning a document handle called if lookup fails. Defaults to repo.create()
65
+ * @param {function?} props.onInvalidDocumentId Function to call if documentId is invalid; signature (error) => (repo, onCreate)
66
+ * @returns {DocHandle} The document handle
67
+ */
68
+ interface UseBootstrapOptions<T> {
69
+ key?: string
70
+ onNoDocument?: (repo: Repo) => DocHandle<T>
71
+ onInvalidDocumentId?(repo: Repo, error: Error): DocHandle<T>
72
+ }
73
+
74
+ export const useBootstrap = <T>({
75
+ key = "documentId",
76
+ onNoDocument = repo => repo.create(),
77
+ onInvalidDocumentId,
78
+ }: UseBootstrapOptions<T> = {}): DocHandle<T> => {
79
+ const repo = useRepo()
80
+ const hash = useHash()
81
+
82
+ // Try to get existing document; else create a new one
83
+ const handle = useMemo((): DocHandle<T> => {
84
+ const existingDocumentId = getDocumentId(key, hash)
85
+ try {
86
+ return existingDocumentId
87
+ ? repo.find(existingDocumentId as DocumentId)
88
+ : onNoDocument(repo)
89
+ } catch (error) {
90
+ // Presumably the documentId was invalid
91
+ if (existingDocumentId && onInvalidDocumentId)
92
+ return onInvalidDocumentId(repo, error)
93
+ // Forward other errors
94
+ throw error
95
+ }
96
+ }, [hash, repo, onNoDocument, onInvalidDocumentId])
97
+
98
+ // Update hashroute & localStorage on changes
99
+ useEffect(() => {
100
+ if (handle) {
101
+ setDocumentId(key, handle.documentId)
102
+ }
103
+ }, [hash, handle])
104
+
105
+ return handle
106
+ }
@@ -14,7 +14,7 @@ export function useDocument<T>(documentId?: DocumentId) {
14
14
 
15
15
  handle.value().then(v => setDoc(v))
16
16
 
17
- const onPatch = (h: DocHandlePatchPayload<T>) => setDoc(h.after)
17
+ const onPatch = (h: DocHandlePatchPayload<T>) => setDoc(h.patchInfo.after)
18
18
  handle.on("patch", onPatch)
19
19
  const cleanup = () => {
20
20
  handle.removeListener("patch", onPatch)