Compare commits
87 Commits
v0.0.32.1
...
39abee7f73
Author | SHA1 | Date | |
---|---|---|---|
39abee7f73 | |||
b770619111 | |||
1c44857da4 | |||
bca4440867 | |||
4855543961 | |||
cb3d6a98b9 | |||
ada67a13d3 | |||
f4c928f26e | |||
91fd515d39 | |||
be6e841d3d | |||
af6afa6903 | |||
6ab5d2a28d | |||
4be033f288 | |||
c550f92003 | |||
ed836b3ee0 | |||
ac7a43abf4 | |||
49f19fce91 | |||
b2197eb8e9 | |||
5fbc2cae1c | |||
730abb49ce | |||
edccab054a | |||
e8210c6fdd | |||
55d69d7c13 | |||
50fb18d4ff | |||
77b1ea1fc8 | |||
61501a9b64 | |||
c1507adac5 | |||
45fb9eda1c | |||
982a61f4bf | |||
18e5b41663 | |||
910c39cbd0 | |||
9952dfd49d | |||
00f75d5382 | |||
d78828554b | |||
b84b561109 | |||
a618815500 | |||
e1f3dc6ae4 | |||
f378db6c6f | |||
7cec0f7d61 | |||
f902d0374c | |||
b5f0a0c4f7 | |||
00623cea09 | |||
ed4f1d6f2c | |||
73f4a3407f | |||
6f11318e84 | |||
e88ee91f0e | |||
3f8daf257c | |||
dc387acadc | |||
68aa41ab96 | |||
85b23437b3 | |||
c59fba817d | |||
c3415ab75c | |||
f1d0151d71 | |||
3c5c1756d1 | |||
6a6b65d1b3 | |||
81bd54dbe6 | |||
6a1bb0d3bc | |||
705e8b553f | |||
e4729b22f2 | |||
662112551a | |||
38fe88aab8 | |||
578c51faa0 | |||
a3ccc73b81 | |||
7312f4d43a | |||
8b546c7e02 | |||
c0b6ff2e64 | |||
638b7cc1e5 | |||
05e54e1be0 | |||
4c3299ead0 | |||
1ef56b35ad | |||
061e79c295 | |||
5edfe732b1 | |||
a8f9b67f71 | |||
de7fbf1eb7 | |||
a51a3d7e43 | |||
433b3b1003 | |||
6703c5b584 | |||
5f729efabe | |||
b2085b3f28 | |||
2f893494b0 | |||
e26af21f63 | |||
7e1d738f8d | |||
199448e11e | |||
fdaabab807 | |||
ca4560c5c9 | |||
2478f3064d | |||
e9b8b43e7c |
365
Doxyfile
365
Doxyfile
@@ -1,4 +1,4 @@
|
|||||||
# Doxyfile 1.9.8
|
# Doxyfile 1.9.4
|
||||||
|
|
||||||
# This file describes the settings to be used by the documentation system
|
# This file describes the settings to be used by the documentation system
|
||||||
# doxygen (www.doxygen.org) for a project.
|
# doxygen (www.doxygen.org) for a project.
|
||||||
@@ -19,8 +19,7 @@
|
|||||||
# configuration file:
|
# configuration file:
|
||||||
# doxygen -x [configFile]
|
# doxygen -x [configFile]
|
||||||
# Use doxygen to compare the used configuration file with the template
|
# Use doxygen to compare the used configuration file with the template
|
||||||
# configuration file without replacing the environment variables or CMake type
|
# configuration file without replacing the environment variables:
|
||||||
# replacement variables:
|
|
||||||
# doxygen -x_noenv [configFile]
|
# doxygen -x_noenv [configFile]
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -86,7 +85,7 @@ CREATE_SUBDIRS = NO
|
|||||||
# level increment doubles the number of directories, resulting in 4096
|
# level increment doubles the number of directories, resulting in 4096
|
||||||
# directories at level 8 which is the default and also the maximum value. The
|
# directories at level 8 which is the default and also the maximum value. The
|
||||||
# sub-directories are organized in 2 levels, the first level always has a fixed
|
# sub-directories are organized in 2 levels, the first level always has a fixed
|
||||||
# number of 16 directories.
|
# numer of 16 directories.
|
||||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
# Minimum value: 0, maximum value: 8, default value: 8.
|
||||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
||||||
|
|
||||||
@@ -342,7 +341,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
|||||||
#
|
#
|
||||||
# Note see also the list of default file extension mappings.
|
# Note see also the list of default file extension mappings.
|
||||||
|
|
||||||
EXTENSION_MAPPING =
|
EXTENSION_MAPPING = js=javascript
|
||||||
|
|
||||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||||
# according to the Markdown format, which allows for more readable
|
# according to the Markdown format, which allows for more readable
|
||||||
@@ -363,17 +362,6 @@ MARKDOWN_SUPPORT = YES
|
|||||||
|
|
||||||
TOC_INCLUDE_HEADINGS = 5
|
TOC_INCLUDE_HEADINGS = 5
|
||||||
|
|
||||||
# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
|
|
||||||
# generate identifiers for the Markdown headings. Note: Every identifier is
|
|
||||||
# unique.
|
|
||||||
# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
|
|
||||||
# sequence number starting at 0 and GITHUB use the lower case version of title
|
|
||||||
# with any whitespace replaced by '-' and punctuation characters removed.
|
|
||||||
# The default value is: DOXYGEN.
|
|
||||||
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
|
|
||||||
|
|
||||||
MARKDOWN_ID_STYLE = DOXYGEN
|
|
||||||
|
|
||||||
# When enabled doxygen tries to link words that correspond to documented
|
# When enabled doxygen tries to link words that correspond to documented
|
||||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
# classes, or namespaces to their corresponding documentation. Such a link can
|
||||||
# be prevented in individual cases by putting a % sign in front of the word or
|
# be prevented in individual cases by putting a % sign in front of the word or
|
||||||
@@ -498,14 +486,6 @@ LOOKUP_CACHE_SIZE = 0
|
|||||||
|
|
||||||
NUM_PROC_THREADS = 1
|
NUM_PROC_THREADS = 1
|
||||||
|
|
||||||
# If the TIMESTAMP tag is set different from NO then each generated page will
|
|
||||||
# contain the date or date and time when the page was generated. Setting this to
|
|
||||||
# NO can help when comparing the output of multiple runs.
|
|
||||||
# Possible values are: YES, NO, DATETIME and DATE.
|
|
||||||
# The default value is: NO.
|
|
||||||
|
|
||||||
TIMESTAMP = NO
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Build related configuration options
|
# Build related configuration options
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -587,8 +567,7 @@ HIDE_UNDOC_MEMBERS = NO
|
|||||||
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
||||||
# undocumented classes that are normally visible in the class hierarchy. If set
|
# undocumented classes that are normally visible in the class hierarchy. If set
|
||||||
# to NO, these classes will be included in the various overviews. This option
|
# to NO, these classes will be included in the various overviews. This option
|
||||||
# will also hide undocumented C++ concepts if enabled. This option has no effect
|
# has no effect if EXTRACT_ALL is enabled.
|
||||||
# if EXTRACT_ALL is enabled.
|
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
HIDE_UNDOC_CLASSES = NO
|
HIDE_UNDOC_CLASSES = NO
|
||||||
@@ -626,8 +605,7 @@ INTERNAL_DOCS = NO
|
|||||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
# Windows (including Cygwin) and MacOS, users should typically set this option
|
||||||
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||||
# YES.
|
# YES.
|
||||||
# Possible values are: SYSTEM, NO and YES.
|
# The default value is: system dependent.
|
||||||
# The default value is: SYSTEM.
|
|
||||||
|
|
||||||
CASE_SENSE_NAMES = YES
|
CASE_SENSE_NAMES = YES
|
||||||
|
|
||||||
@@ -879,26 +857,11 @@ WARN_IF_INCOMPLETE_DOC = YES
|
|||||||
|
|
||||||
WARN_NO_PARAMDOC = NO
|
WARN_NO_PARAMDOC = NO
|
||||||
|
|
||||||
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
|
|
||||||
# undocumented enumeration values. If set to NO, doxygen will accept
|
|
||||||
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
|
|
||||||
# will automatically be disabled.
|
|
||||||
# The default value is: NO.
|
|
||||||
|
|
||||||
WARN_IF_UNDOC_ENUM_VAL = NO
|
|
||||||
|
|
||||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||||
# at the end of the doxygen process doxygen will return with a non-zero status.
|
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||||
# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
|
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
||||||
# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
|
|
||||||
# write the warning messages in between other messages but write them at the end
|
|
||||||
# of a run, in case a WARN_LOGFILE is defined the warning messages will be
|
|
||||||
# besides being in the defined file also be shown at the end of a run, unless
|
|
||||||
# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
|
|
||||||
# the behavior will remain as with the setting FAIL_ON_WARNINGS.
|
|
||||||
# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
|
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
WARN_AS_ERROR = NO
|
WARN_AS_ERROR = NO
|
||||||
@@ -944,6 +907,11 @@ WARN_LOGFILE =
|
|||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
INPUT = README.md \
|
INPUT = README.md \
|
||||||
|
core/app.js \
|
||||||
|
core/client.js \
|
||||||
|
core/core.js \
|
||||||
|
core/http.js \
|
||||||
|
core/tfrpc.js \
|
||||||
docs/ \
|
docs/ \
|
||||||
src/
|
src/
|
||||||
|
|
||||||
@@ -952,21 +920,10 @@ INPUT = README.md \
|
|||||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||||
# documentation (see:
|
# documentation (see:
|
||||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||||
# See also: INPUT_FILE_ENCODING
|
|
||||||
# The default value is: UTF-8.
|
# The default value is: UTF-8.
|
||||||
|
|
||||||
INPUT_ENCODING = UTF-8
|
INPUT_ENCODING = UTF-8
|
||||||
|
|
||||||
# This tag can be used to specify the character encoding of the source files
|
|
||||||
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
|
|
||||||
# character encoding on a per file pattern basis. Doxygen will compare the file
|
|
||||||
# name with each pattern and apply the encoding instead of the default
|
|
||||||
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
|
|
||||||
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
|
|
||||||
# "INPUT_ENCODING" for further information on supported encodings.
|
|
||||||
|
|
||||||
INPUT_FILE_ENCODING =
|
|
||||||
|
|
||||||
# If the value of the INPUT tag contains directories, you can use the
|
# If the value of the INPUT tag contains directories, you can use the
|
||||||
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
||||||
# *.h) to filter out the source-files in the directories.
|
# *.h) to filter out the source-files in the directories.
|
||||||
@@ -978,14 +935,15 @@ INPUT_FILE_ENCODING =
|
|||||||
# Note the list of default checked file patterns might differ from the list of
|
# Note the list of default checked file patterns might differ from the list of
|
||||||
# default file extension mappings.
|
# default file extension mappings.
|
||||||
#
|
#
|
||||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
|
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||||
# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl,
|
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||||
# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php,
|
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
||||||
# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be
|
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
||||||
# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||||
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||||
|
|
||||||
FILE_PATTERNS = *.h \
|
FILE_PATTERNS = *.h \
|
||||||
|
*.js \
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||||
@@ -1024,6 +982,9 @@ EXCLUDE_PATTERNS =
|
|||||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||||
# ANamespace::AClass, ANamespace::*Test
|
# ANamespace::AClass, ANamespace::*Test
|
||||||
|
#
|
||||||
|
# Note that the wildcards are matched against the file with absolute path, so to
|
||||||
|
# exclude all test directories use the pattern */test/*
|
||||||
|
|
||||||
EXCLUDE_SYMBOLS =
|
EXCLUDE_SYMBOLS =
|
||||||
|
|
||||||
@@ -1068,11 +1029,6 @@ IMAGE_PATH = docs/images/
|
|||||||
# code is scanned, but not when the output code is generated. If lines are added
|
# code is scanned, but not when the output code is generated. If lines are added
|
||||||
# or removed, the anchors will not be placed correctly.
|
# or removed, the anchors will not be placed correctly.
|
||||||
#
|
#
|
||||||
# Note that doxygen will use the data processed and written to standard output
|
|
||||||
# for further processing, therefore nothing else, like debug statements or used
|
|
||||||
# commands (so in case of a Windows batch file always use @echo OFF), should be
|
|
||||||
# written to standard output.
|
|
||||||
#
|
|
||||||
# Note that for custom extensions or not directly supported extensions you also
|
# Note that for custom extensions or not directly supported extensions you also
|
||||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||||
# properly processed by doxygen.
|
# properly processed by doxygen.
|
||||||
@@ -1114,15 +1070,6 @@ FILTER_SOURCE_PATTERNS =
|
|||||||
|
|
||||||
USE_MDFILE_AS_MAINPAGE = README.md
|
USE_MDFILE_AS_MAINPAGE = README.md
|
||||||
|
|
||||||
# The Fortran standard specifies that for fixed formatted Fortran code all
|
|
||||||
# characters from position 72 are to be considered as comment. A common
|
|
||||||
# extension is to allow longer lines before the automatic comment starts. The
|
|
||||||
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
|
|
||||||
# be processed before the automatic comment starts.
|
|
||||||
# Minimum value: 7, maximum value: 10000, default value: 72.
|
|
||||||
|
|
||||||
FORTRAN_COMMENT_AFTER = 72
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Configuration options related to source browsing
|
# Configuration options related to source browsing
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -1260,11 +1207,10 @@ CLANG_DATABASE_PATH =
|
|||||||
|
|
||||||
ALPHABETICAL_INDEX = YES
|
ALPHABETICAL_INDEX = YES
|
||||||
|
|
||||||
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
|
# In case all classes in a project start with a common prefix, all classes will
|
||||||
# that should be ignored while generating the index headers. The IGNORE_PREFIX
|
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
|
||||||
# tag works for classes, function and member names. The entity will be placed in
|
# can be used to specify a prefix (or a list of prefixes) that should be ignored
|
||||||
# the alphabetical list under the first letter of the entity name that remains
|
# while generating the index headers.
|
||||||
# after removing the prefix.
|
|
||||||
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
||||||
|
|
||||||
IGNORE_PREFIX =
|
IGNORE_PREFIX =
|
||||||
@@ -1343,12 +1289,7 @@ HTML_STYLESHEET =
|
|||||||
# Doxygen will copy the style sheet files to the output directory.
|
# Doxygen will copy the style sheet files to the output directory.
|
||||||
# Note: The order of the extra style sheet files is of importance (e.g. the last
|
# Note: The order of the extra style sheet files is of importance (e.g. the last
|
||||||
# style sheet in the list overrules the setting of the previous ones in the
|
# style sheet in the list overrules the setting of the previous ones in the
|
||||||
# list).
|
# list). For an example see the documentation.
|
||||||
# Note: Since the styling of scrollbars can currently not be overruled in
|
|
||||||
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
|
|
||||||
# one or more extra stylesheets have been specified. So if scrollbar
|
|
||||||
# customization is desired it has to be added explicitly. For an example see the
|
|
||||||
# documentation.
|
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
HTML_EXTRA_STYLESHEET =
|
HTML_EXTRA_STYLESHEET =
|
||||||
@@ -1363,19 +1304,6 @@ HTML_EXTRA_STYLESHEET =
|
|||||||
|
|
||||||
HTML_EXTRA_FILES =
|
HTML_EXTRA_FILES =
|
||||||
|
|
||||||
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
|
|
||||||
# should be rendered with a dark or light theme.
|
|
||||||
# Possible values are: LIGHT always generate light mode output, DARK always
|
|
||||||
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
|
|
||||||
# the user preference, use light mode if no preference is set (the default),
|
|
||||||
# AUTO_DARK automatically set the mode according to the user preference, use
|
|
||||||
# dark mode if no preference is set and TOGGLE allow to user to switch between
|
|
||||||
# light and dark mode via a button.
|
|
||||||
# The default value is: AUTO_LIGHT.
|
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
|
||||||
|
|
||||||
HTML_COLORSTYLE = AUTO_LIGHT
|
|
||||||
|
|
||||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||||
# will adjust the colors in the style sheet and background images according to
|
# will adjust the colors in the style sheet and background images according to
|
||||||
# this color. Hue is specified as an angle on a color-wheel, see
|
# this color. Hue is specified as an angle on a color-wheel, see
|
||||||
@@ -1406,6 +1334,15 @@ HTML_COLORSTYLE_SAT = 100
|
|||||||
|
|
||||||
HTML_COLORSTYLE_GAMMA = 80
|
HTML_COLORSTYLE_GAMMA = 80
|
||||||
|
|
||||||
|
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
|
||||||
|
# page will contain the date and time when the page was generated. Setting this
|
||||||
|
# to YES can help to show when doxygen was last run and thus if the
|
||||||
|
# documentation is up to date.
|
||||||
|
# The default value is: NO.
|
||||||
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
|
#HTML_TIMESTAMP = NO
|
||||||
|
|
||||||
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
|
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
|
||||||
# documentation will contain a main index with vertical navigation menus that
|
# documentation will contain a main index with vertical navigation menus that
|
||||||
# are dynamically created via JavaScript. If disabled, the navigation index will
|
# are dynamically created via JavaScript. If disabled, the navigation index will
|
||||||
@@ -1425,13 +1362,6 @@ HTML_DYNAMIC_MENUS = YES
|
|||||||
|
|
||||||
HTML_DYNAMIC_SECTIONS = NO
|
HTML_DYNAMIC_SECTIONS = NO
|
||||||
|
|
||||||
# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
|
|
||||||
# dynamically folded and expanded in the generated HTML source code.
|
|
||||||
# The default value is: YES.
|
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
|
||||||
|
|
||||||
HTML_CODE_FOLDING = YES
|
|
||||||
|
|
||||||
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
|
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
|
||||||
# shown in the various tree structured indices initially; the user can expand
|
# shown in the various tree structured indices initially; the user can expand
|
||||||
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
||||||
@@ -1562,16 +1492,6 @@ BINARY_TOC = NO
|
|||||||
|
|
||||||
TOC_EXPAND = NO
|
TOC_EXPAND = NO
|
||||||
|
|
||||||
# The SITEMAP_URL tag is used to specify the full URL of the place where the
|
|
||||||
# generated documentation will be placed on the server by the user during the
|
|
||||||
# deployment of the documentation. The generated sitemap is called sitemap.xml
|
|
||||||
# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
|
|
||||||
# is specified no sitemap is generated. For information about the sitemap
|
|
||||||
# protocol see https://www.sitemaps.org
|
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
|
||||||
|
|
||||||
SITEMAP_URL =
|
|
||||||
|
|
||||||
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
|
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
|
||||||
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
|
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
|
||||||
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
|
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
|
||||||
@@ -1747,6 +1667,17 @@ HTML_FORMULA_FORMAT = png
|
|||||||
|
|
||||||
FORMULA_FONTSIZE = 10
|
FORMULA_FONTSIZE = 10
|
||||||
|
|
||||||
|
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
|
||||||
|
# generated for formulas are transparent PNGs. Transparent PNGs are not
|
||||||
|
# supported properly for IE 6.0, but are supported on all modern browsers.
|
||||||
|
#
|
||||||
|
# Note that when changing this option you need to delete any form_*.png files in
|
||||||
|
# the HTML output directory before the changes have effect.
|
||||||
|
# The default value is: YES.
|
||||||
|
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||||
|
|
||||||
|
#FORMULA_TRANSPARENT = YES
|
||||||
|
|
||||||
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
|
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
|
||||||
# to create new LaTeX commands to be used in formulas as building blocks. See
|
# to create new LaTeX commands to be used in formulas as building blocks. See
|
||||||
# the section "Including formulas" for details.
|
# the section "Including formulas" for details.
|
||||||
@@ -1808,8 +1739,8 @@ MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2
|
|||||||
|
|
||||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||||
# extension names that should be enabled during MathJax rendering. For example
|
# extension names that should be enabled during MathJax rendering. For example
|
||||||
# for MathJax version 2 (see
|
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html
|
||||||
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
# #tex-and-latex-extensions):
|
||||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||||
# For example for MathJax version 3 (see
|
# For example for MathJax version 3 (see
|
||||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||||
@@ -2060,16 +1991,9 @@ PDF_HYPERLINKS = YES
|
|||||||
|
|
||||||
USE_PDFLATEX = YES
|
USE_PDFLATEX = YES
|
||||||
|
|
||||||
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
|
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||||
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
|
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||||
# mode nothing is printed on the terminal, errors are scrolled as if <return> is
|
# if errors occur, instead of asking the user for help.
|
||||||
# hit at every error; missing files that TeX tries to input or request from
|
|
||||||
# keyboard input (\read on a not open input stream) cause the job to abort,
|
|
||||||
# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
|
|
||||||
# but there is no possibility of user interaction just like in batch mode,
|
|
||||||
# SCROLL In scroll mode, TeX will stop only for missing files to input or if
|
|
||||||
# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
|
|
||||||
# each error, asking for user intervention.
|
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||||
|
|
||||||
@@ -2090,6 +2014,14 @@ LATEX_HIDE_INDICES = NO
|
|||||||
|
|
||||||
LATEX_BIB_STYLE = plain
|
LATEX_BIB_STYLE = plain
|
||||||
|
|
||||||
|
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
|
||||||
|
# page will contain the date and time when the page was generated. Setting this
|
||||||
|
# to NO can help when comparing the output of multiple runs.
|
||||||
|
# The default value is: NO.
|
||||||
|
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||||
|
|
||||||
|
#LATEX_TIMESTAMP = NO
|
||||||
|
|
||||||
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
|
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
|
||||||
# path from which the emoji images will be read. If a relative path is entered,
|
# path from which the emoji images will be read. If a relative path is entered,
|
||||||
# it will be relative to the LATEX_OUTPUT directory. If left blank the
|
# it will be relative to the LATEX_OUTPUT directory. If left blank the
|
||||||
@@ -2255,39 +2187,13 @@ DOCBOOK_OUTPUT = docbook
|
|||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
||||||
# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
|
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
|
||||||
# the structure of the code including all documentation. Note that this feature
|
# the structure of the code including all documentation. Note that this feature
|
||||||
# is still experimental and incomplete at the moment.
|
# is still experimental and incomplete at the moment.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
GENERATE_AUTOGEN_DEF = NO
|
GENERATE_AUTOGEN_DEF = NO
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
# Configuration options related to Sqlite3 output
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
|
|
||||||
# database with symbols found by doxygen stored in tables.
|
|
||||||
# The default value is: NO.
|
|
||||||
|
|
||||||
#GENERATE_SQLITE3 = NO
|
|
||||||
|
|
||||||
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
|
|
||||||
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
|
|
||||||
# in front of it.
|
|
||||||
# The default directory is: sqlite3.
|
|
||||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
|
||||||
|
|
||||||
#SQLITE3_OUTPUT = sqlite3
|
|
||||||
|
|
||||||
# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
|
|
||||||
# database file will be recreated with each doxygen run. If set to NO, doxygen
|
|
||||||
# will warn if an a database file is already found and not modify it.
|
|
||||||
# The default value is: YES.
|
|
||||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
|
||||||
|
|
||||||
#SQLITE3_RECREATE_DB = YES
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Configuration options related to the Perl module output
|
# Configuration options related to the Perl module output
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -2430,15 +2336,15 @@ TAGFILES =
|
|||||||
|
|
||||||
GENERATE_TAGFILE =
|
GENERATE_TAGFILE =
|
||||||
|
|
||||||
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
|
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
|
||||||
# will be listed in the class and namespace index. If set to NO, only the
|
# the class index. If set to NO, only the inherited external classes will be
|
||||||
# inherited external classes will be listed.
|
# listed.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
ALLEXTERNALS = NO
|
ALLEXTERNALS = NO
|
||||||
|
|
||||||
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
||||||
# in the topic index. If set to NO, only the current project's groups will be
|
# in the modules index. If set to NO, only the current project's groups will be
|
||||||
# listed.
|
# listed.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
@@ -2452,9 +2358,16 @@ EXTERNAL_GROUPS = YES
|
|||||||
EXTERNAL_PAGES = YES
|
EXTERNAL_PAGES = YES
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Configuration options related to diagram generator tools
|
# Configuration options related to the dot tool
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||||
|
# then run dia to produce the diagram and insert it in the documentation. The
|
||||||
|
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||||
|
# If left empty dia is assumed to be found in the default search path.
|
||||||
|
|
||||||
|
DIA_PATH =
|
||||||
|
|
||||||
# If set to YES the inheritance and collaboration graphs will hide inheritance
|
# If set to YES the inheritance and collaboration graphs will hide inheritance
|
||||||
# and usage relations if the target is undocumented or is not a class.
|
# and usage relations if the target is undocumented or is not a class.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
@@ -2463,10 +2376,10 @@ HIDE_UNDOC_RELATIONS = YES
|
|||||||
|
|
||||||
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
|
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
|
||||||
# available from the path. This tool is part of Graphviz (see:
|
# available from the path. This tool is part of Graphviz (see:
|
||||||
# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||||
# Bell Labs. The other options in this section have no effect if this option is
|
# Bell Labs. The other options in this section have no effect if this option is
|
||||||
# set to NO
|
# set to NO
|
||||||
# The default value is: YES.
|
# The default value is: NO.
|
||||||
|
|
||||||
HAVE_DOT = YES
|
HAVE_DOT = YES
|
||||||
|
|
||||||
@@ -2480,51 +2393,37 @@ HAVE_DOT = YES
|
|||||||
|
|
||||||
DOT_NUM_THREADS = 0
|
DOT_NUM_THREADS = 0
|
||||||
|
|
||||||
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
|
# When you want a differently looking font in the dot files that doxygen
|
||||||
# subgraphs. When you want a differently looking font in the dot files that
|
# generates you can specify the font name using DOT_FONTNAME. You need to make
|
||||||
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
|
# sure dot is able to find the font, which can be done by putting it in a
|
||||||
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
|
# standard location or by setting the DOTFONTPATH environment variable or by
|
||||||
# Edge and Graph Attributes specification</a> You need to make sure dot is able
|
# setting DOT_FONTPATH to the directory containing the font.
|
||||||
# to find the font, which can be done by putting it in a standard location or by
|
# The default value is: Helvetica.
|
||||||
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
|
|
||||||
# directory containing the font. Default graphviz fontsize is 14.
|
|
||||||
# The default value is: fontname=Helvetica,fontsize=10.
|
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
|
#DOT_FONTNAME = Helvetica
|
||||||
|
|
||||||
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
|
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
||||||
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
|
# dot graphs.
|
||||||
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
|
# Minimum value: 4, maximum value: 24, default value: 10.
|
||||||
# arrows shapes.</a>
|
|
||||||
# The default value is: labelfontname=Helvetica,labelfontsize=10.
|
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
|
#DOT_FONTSIZE = 10
|
||||||
|
|
||||||
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
|
# By default doxygen will tell dot to use the default font as specified with
|
||||||
# around nodes set 'shape=plain' or 'shape=plaintext' <a
|
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
||||||
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
|
# the path where dot can find it using this tag.
|
||||||
# The default value is: shape=box,height=0.2,width=0.4.
|
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
#DOT_FONTPATH =
|
||||||
|
|
||||||
# You can set the path where dot can find font specified with fontname in
|
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
||||||
# DOT_COMMON_ATTR and others dot attributes.
|
# graph for each documented class showing the direct and indirect inheritance
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
||||||
|
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
||||||
DOT_FONTPATH =
|
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
||||||
|
# links.
|
||||||
# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
|
# Possible values are: NO, YES, TEXT and GRAPH.
|
||||||
# generate a graph for each documented class showing the direct and indirect
|
|
||||||
# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
|
|
||||||
# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
|
|
||||||
# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
|
|
||||||
# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
|
|
||||||
# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
|
|
||||||
# relations will be shown as texts / links.
|
|
||||||
# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
CLASS_GRAPH = YES
|
CLASS_GRAPH = YES
|
||||||
@@ -2532,21 +2431,15 @@ CLASS_GRAPH = YES
|
|||||||
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
|
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
|
||||||
# graph for each documented class showing the direct and indirect implementation
|
# graph for each documented class showing the direct and indirect implementation
|
||||||
# dependencies (inheritance, containment, and class references variables) of the
|
# dependencies (inheritance, containment, and class references variables) of the
|
||||||
# class with other documented classes. Explicit enabling a collaboration graph,
|
# class with other documented classes.
|
||||||
# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
|
|
||||||
# command \collaborationgraph. Disabling a collaboration graph can be
|
|
||||||
# accomplished by means of the command \hidecollaborationgraph.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
COLLABORATION_GRAPH = YES
|
COLLABORATION_GRAPH = YES
|
||||||
|
|
||||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
||||||
# groups, showing the direct groups dependencies. Explicit enabling a group
|
# groups, showing the direct groups dependencies. See also the chapter Grouping
|
||||||
# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
|
# in the manual.
|
||||||
# of the command \groupgraph. Disabling a directory graph can be accomplished by
|
|
||||||
# means of the command \hidegroupgraph. See also the chapter Grouping in the
|
|
||||||
# manual.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2606,9 +2499,7 @@ TEMPLATE_RELATIONS = NO
|
|||||||
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
|
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
|
||||||
# YES then doxygen will generate a graph for each documented file showing the
|
# YES then doxygen will generate a graph for each documented file showing the
|
||||||
# direct and indirect include dependencies of the file with other documented
|
# direct and indirect include dependencies of the file with other documented
|
||||||
# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
|
# files.
|
||||||
# can be accomplished by means of the command \includegraph. Disabling an
|
|
||||||
# include graph can be accomplished by means of the command \hideincludegraph.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2617,10 +2508,7 @@ INCLUDE_GRAPH = YES
|
|||||||
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
|
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
|
||||||
# set to YES then doxygen will generate a graph for each documented file showing
|
# set to YES then doxygen will generate a graph for each documented file showing
|
||||||
# the direct and indirect include dependencies of the file with other documented
|
# the direct and indirect include dependencies of the file with other documented
|
||||||
# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
|
# files.
|
||||||
# to NO, can be accomplished by means of the command \includedbygraph. Disabling
|
|
||||||
# an included by graph can be accomplished by means of the command
|
|
||||||
# \hideincludedbygraph.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2660,10 +2548,7 @@ GRAPHICAL_HIERARCHY = YES
|
|||||||
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
|
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
|
||||||
# dependencies a directory has on other directories in a graphical way. The
|
# dependencies a directory has on other directories in a graphical way. The
|
||||||
# dependency relations are determined by the #include relations between the
|
# dependency relations are determined by the #include relations between the
|
||||||
# files in the directories. Explicit enabling a directory graph, when
|
# files in the directories.
|
||||||
# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
|
|
||||||
# \directorygraph. Disabling a directory graph can be accomplished by means of
|
|
||||||
# the command \hidedirectorygraph.
|
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
@@ -2679,13 +2564,12 @@ DIR_GRAPH_MAX_DEPTH = 1
|
|||||||
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
||||||
# generated by dot. For an explanation of the image formats see the section
|
# generated by dot. For an explanation of the image formats see the section
|
||||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
# output formats in the documentation of the dot tool (Graphviz (see:
|
||||||
# https://www.graphviz.org/)).
|
# http://www.graphviz.org/)).
|
||||||
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
||||||
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||||
# requirement).
|
# requirement).
|
||||||
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
|
# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
|
||||||
# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
|
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||||
# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
|
||||||
# png:gdiplus:gdiplus.
|
# png:gdiplus:gdiplus.
|
||||||
# The default value is: png.
|
# The default value is: png.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
@@ -2717,12 +2601,11 @@ DOT_PATH =
|
|||||||
|
|
||||||
DOTFILE_DIRS =
|
DOTFILE_DIRS =
|
||||||
|
|
||||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||||
# then run dia to produce the diagram and insert it in the documentation. The
|
# contain msc files that are included in the documentation (see the \mscfile
|
||||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
# command).
|
||||||
# If left empty dia is assumed to be found in the default search path.
|
|
||||||
|
|
||||||
DIA_PATH =
|
MSCFILE_DIRS =
|
||||||
|
|
||||||
# The DIAFILE_DIRS tag can be used to specify one or more directories that
|
# The DIAFILE_DIRS tag can be used to specify one or more directories that
|
||||||
# contain dia files that are included in the documentation (see the \diafile
|
# contain dia files that are included in the documentation (see the \diafile
|
||||||
@@ -2772,6 +2655,18 @@ DOT_GRAPH_MAX_NODES = 50
|
|||||||
|
|
||||||
MAX_DOT_GRAPH_DEPTH = 0
|
MAX_DOT_GRAPH_DEPTH = 0
|
||||||
|
|
||||||
|
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
|
||||||
|
# background. This is disabled by default, because dot on Windows does not seem
|
||||||
|
# to support this out of the box.
|
||||||
|
#
|
||||||
|
# Warning: Depending on the platform used, enabling this option may lead to
|
||||||
|
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
|
||||||
|
# read).
|
||||||
|
# The default value is: NO.
|
||||||
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
|
#DOT_TRANSPARENT = NO
|
||||||
|
|
||||||
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
|
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
|
||||||
# files in one run (i.e. multiple -o and -T options on the command line). This
|
# files in one run (i.e. multiple -o and -T options on the command line). This
|
||||||
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
|
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
|
||||||
@@ -2799,19 +2694,3 @@ GENERATE_LEGEND = YES
|
|||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
DOT_CLEANUP = YES
|
DOT_CLEANUP = YES
|
||||||
|
|
||||||
# You can define message sequence charts within doxygen comments using the \msc
|
|
||||||
# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
|
|
||||||
# use a built-in version of mscgen tool to produce the charts. Alternatively,
|
|
||||||
# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
|
|
||||||
# specifying prog as the value, doxygen will call the tool as prog -T
|
|
||||||
# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
|
|
||||||
# output file formats "png", "eps", "svg", and "ismap".
|
|
||||||
|
|
||||||
MSCGEN_TOOL =
|
|
||||||
|
|
||||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
|
||||||
# contain msc files that are included in the documentation (see the \mscfile
|
|
||||||
# command).
|
|
||||||
|
|
||||||
MSCFILE_DIRS =
|
|
||||||
|
18
GNUmakefile
18
GNUmakefile
@@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
|
|||||||
## LD := Linker.
|
## LD := Linker.
|
||||||
## ANDROID_SDK := Path to the Android SDK.
|
## ANDROID_SDK := Path to the Android SDK.
|
||||||
|
|
||||||
VERSION_CODE := 39
|
VERSION_CODE := 43
|
||||||
VERSION_CODE_IOS := 15
|
VERSION_CODE_IOS := 17
|
||||||
VERSION_NUMBER := 0.0.32.1
|
VERSION_NUMBER := 0.2025.9-wip
|
||||||
VERSION_NAME := This program kills fascists.
|
VERSION_NAME := This program kills fascists.
|
||||||
|
|
||||||
IPHONEOS_VERSION_MIN=14.0
|
IPHONEOS_VERSION_MIN=14.0
|
||||||
|
|
||||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500200.zip
|
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500400.zip
|
||||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
||||||
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||||
@@ -253,7 +253,10 @@ $(ANDROID_TARGETS): CFLAGS += \
|
|||||||
-fno-asynchronous-unwind-tables \
|
-fno-asynchronous-unwind-tables \
|
||||||
-funwind-tables \
|
-funwind-tables \
|
||||||
-Wno-unknown-warning-option
|
-Wno-unknown-warning-option
|
||||||
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
$(ANDROID_TARGETS): LDFLAGS += \
|
||||||
|
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
||||||
|
-Wl,-z,max-page-size=16384 \
|
||||||
|
-fPIC
|
||||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||||
$(DEBUG_TARGETS): LDFLAGS += -Og
|
$(DEBUG_TARGETS): LDFLAGS += -Og
|
||||||
$(RELEASE_TARGETS): CFLAGS += \
|
$(RELEASE_TARGETS): CFLAGS += \
|
||||||
@@ -1140,6 +1143,11 @@ releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a rele
|
|||||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||||
.PHONY: releaseapkgo
|
.PHONY: releaseapkgo
|
||||||
|
|
||||||
|
x86releaseapkgo: out/TildeFriends-x86-release.apk ## Build, install, and run an x86 release Android APK.
|
||||||
|
@adb install -r $<
|
||||||
|
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||||
|
.PHONY: x86releaseapkgo
|
||||||
|
|
||||||
apklog: ## Display Android log output.
|
apklog: ## Display Android log output.
|
||||||
@adb logcat *:S tildefriends
|
@adb logcat *:S tildefriends
|
||||||
.PHONY: apklog
|
.PHONY: apklog
|
||||||
|
8
apps/blog/lit-all.min.js
vendored
8
apps/blog/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/issues/lit-all.min.js
vendored
8
apps/issues/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/journal/lit-all.min.js
vendored
8
apps/journal/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/sneaker/lit-all.min.js
vendored
8
apps/sneaker/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&Ym1vefMN4CV4UIgLuV+zu52qj58WwIScctt4v5YIHmQ=.sha256"
|
"previous": "&C4r2tB/tzPjPSrS3NRiebaNOP94G4FX80yIg7YN9sBQ=.sha256"
|
||||||
}
|
}
|
||||||
|
8
apps/ssb/lit-all.min.js
vendored
8
apps/ssb/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -21,10 +21,13 @@ class TfElement extends LitElement {
|
|||||||
channels_latest: {type: Object},
|
channels_latest: {type: Object},
|
||||||
guest: {type: Boolean},
|
guest: {type: Boolean},
|
||||||
url: {type: String},
|
url: {type: String},
|
||||||
|
private_closed: {type: Object},
|
||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
|
grouped_private_messages: {type: Object},
|
||||||
recent_reactions: {type: Array},
|
recent_reactions: {type: Array},
|
||||||
is_administrator: {type: Boolean},
|
is_administrator: {type: Boolean},
|
||||||
stay_connected: {type: Boolean},
|
stay_connected: {type: Boolean},
|
||||||
|
progress: {type: Number},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ class TfElement extends LitElement {
|
|||||||
this.loading_latest = 0;
|
this.loading_latest = 0;
|
||||||
this.loading_latest_scheduled = 0;
|
this.loading_latest_scheduled = 0;
|
||||||
this.recent_reactions = [];
|
this.recent_reactions = [];
|
||||||
|
this.private_closed = {};
|
||||||
tfrpc.rpc.getBroadcasts().then((b) => {
|
tfrpc.rpc.getBroadcasts().then((b) => {
|
||||||
self.broadcasts = b || [];
|
self.broadcasts = b || [];
|
||||||
});
|
});
|
||||||
@@ -56,6 +60,7 @@ class TfElement extends LitElement {
|
|||||||
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
||||||
tfrpc.register(function hashChanged(hash) {
|
tfrpc.register(function hashChanged(hash) {
|
||||||
self.set_hash(hash);
|
self.set_hash(hash);
|
||||||
|
self.reset_progress();
|
||||||
});
|
});
|
||||||
tfrpc.register(async function notifyNewMessage(id) {
|
tfrpc.register(async function notifyNewMessage(id) {
|
||||||
await self.fetch_new_message(id);
|
await self.fetch_new_message(id);
|
||||||
@@ -83,9 +88,22 @@ class TfElement extends LitElement {
|
|||||||
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
||||||
this.guest = !this.whoami?.length;
|
this.guest = !this.whoami?.length;
|
||||||
this.ids = ids;
|
this.ids = ids;
|
||||||
|
let private_closed =
|
||||||
|
(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}';
|
||||||
|
this.private_closed = JSON.parse(private_closed);
|
||||||
await this.load_channels();
|
await this.load_channels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async close_private_chat(event) {
|
||||||
|
let update = {};
|
||||||
|
update[event.detail.key] = true;
|
||||||
|
this.private_closed = Object.assign(update, this.private_closed);
|
||||||
|
await tfrpc.rpc.databaseSet(
|
||||||
|
'private_closed',
|
||||||
|
JSON.stringify(this.private_closed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async load_channels() {
|
async load_channels() {
|
||||||
let channels = await tfrpc.rpc.query(
|
let channels = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
@@ -133,12 +151,32 @@ class TfElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visible_private() {
|
||||||
|
if (!this.grouped_private_messages || !this.private_closed) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let self = this;
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(this.grouped_private_messages).filter(([key, value]) => {
|
||||||
|
let channel = '🔐' + [...new Set(JSON.parse(key))].sort().join(',');
|
||||||
|
let grouped_latest = Math.max(...value.map((x) => x.rowid));
|
||||||
|
return (
|
||||||
|
!self.private_closed[key] ||
|
||||||
|
self.channels_unread[channel] === undefined ||
|
||||||
|
grouped_latest > self.channels_unread[channel]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
next_channel(delta) {
|
next_channel(delta) {
|
||||||
let channel_names = [
|
let channel_names = [
|
||||||
'',
|
'',
|
||||||
'@',
|
'@',
|
||||||
'👍',
|
'👍',
|
||||||
'🔐',
|
...Object.keys(this.visible_private())
|
||||||
|
.sort()
|
||||||
|
.map((x) => '🔐' + JSON.parse(x).join(',')),
|
||||||
...this.channels.map((x) => '#' + x),
|
...this.channels.map((x) => '#' + x),
|
||||||
];
|
];
|
||||||
let index = channel_names.indexOf(this.hash.substring(1));
|
let index = channel_names.indexOf(this.hash.substring(1));
|
||||||
@@ -364,6 +402,36 @@ class TfElement extends LitElement {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async group_private_messages(messages) {
|
||||||
|
let groups = {};
|
||||||
|
let result = await this.decrypt(
|
||||||
|
await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content
|
||||||
|
FROM messages
|
||||||
|
JOIN json_each(?) AS ids
|
||||||
|
WHERE messages.id = ids.value
|
||||||
|
ORDER BY timestamp DESC
|
||||||
|
`,
|
||||||
|
[JSON.stringify(messages)]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for (let message of result) {
|
||||||
|
let key = JSON.stringify(
|
||||||
|
[
|
||||||
|
...new Set(
|
||||||
|
message?.decrypted?.recps?.filter((x) => x != this.whoami)
|
||||||
|
),
|
||||||
|
].sort() ?? []
|
||||||
|
);
|
||||||
|
if (!groups[key]) {
|
||||||
|
groups[key] = [];
|
||||||
|
}
|
||||||
|
groups[key].push(message);
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
async load_channels_latest(following) {
|
async load_channels_latest(following) {
|
||||||
let start_time = new Date();
|
let start_time = new Date();
|
||||||
let latest_private = this.get_latest_private(following);
|
let latest_private = this.get_latest_private(following);
|
||||||
@@ -436,12 +504,15 @@ class TfElement extends LitElement {
|
|||||||
console.log('channels took', (new Date() - start_time) / 1000.0);
|
console.log('channels took', (new Date() - start_time) / 1000.0);
|
||||||
let self = this;
|
let self = this;
|
||||||
start_time = new Date();
|
start_time = new Date();
|
||||||
latest_private.then(function (latest) {
|
latest_private.then(async function (latest) {
|
||||||
self.channels_latest = Object.assign({}, self.channels_latest, {
|
self.channels_latest = Object.assign({}, self.channels_latest, {
|
||||||
'🔐': latest[0],
|
'🔐': latest[0],
|
||||||
});
|
});
|
||||||
console.log('private took', (new Date() - start_time) / 1000.0);
|
console.log('private took', (new Date() - start_time) / 1000.0);
|
||||||
self.private_messages = latest[1];
|
self.private_messages = latest[1];
|
||||||
|
self.grouped_private_messages = await self.group_private_messages(
|
||||||
|
latest[1]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +521,28 @@ class TfElement extends LitElement {
|
|||||||
this.schedule_load_latest();
|
this.schedule_load_latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset_progress() {
|
||||||
|
if (this.progress === undefined) {
|
||||||
|
this._progress_start = new Date();
|
||||||
|
requestAnimationFrame(this.update_progress.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_progress() {
|
||||||
|
if (
|
||||||
|
!this.loading_latest &&
|
||||||
|
!this.loading_latest_scheduled &&
|
||||||
|
!this.shadowRoot.getElementById('tf-tab-news')?.is_loading()
|
||||||
|
) {
|
||||||
|
this.progress = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.progress = (new Date() - this._progress_start).valueOf();
|
||||||
|
requestAnimationFrame(this.update_progress.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
schedule_load_latest() {
|
schedule_load_latest() {
|
||||||
|
this.reset_progress();
|
||||||
if (!this.loading_latest) {
|
if (!this.loading_latest) {
|
||||||
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
||||||
this.load();
|
this.load();
|
||||||
@@ -495,6 +587,7 @@ class TfElement extends LitElement {
|
|||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.loading_latest = true;
|
this.loading_latest = true;
|
||||||
|
this.reset_progress();
|
||||||
try {
|
try {
|
||||||
let start_time = new Date();
|
let start_time = new Date();
|
||||||
let whoami = this.whoami;
|
let whoami = this.whoami;
|
||||||
@@ -603,8 +696,11 @@ class TfElement extends LitElement {
|
|||||||
@channelsetunread=${this.channel_set_unread}
|
@channelsetunread=${this.channel_set_unread}
|
||||||
@refresh=${this.refresh}
|
@refresh=${this.refresh}
|
||||||
@toggle_stay_connected=${this.toggle_stay_connected}
|
@toggle_stay_connected=${this.toggle_stay_connected}
|
||||||
|
@loadmessages=${this.reset_progress}
|
||||||
|
@closeprivatechat=${this.close_private_chat}
|
||||||
.connections=${this.connections}
|
.connections=${this.connections}
|
||||||
.private_messages=${this.private_messages}
|
.private_messages=${this.private_messages}
|
||||||
|
.grouped_private_messages=${this.visible_private()}
|
||||||
.recent_reactions=${this.recent_reactions}
|
.recent_reactions=${this.recent_reactions}
|
||||||
?is_administrator=${this.is_administrator}
|
?is_administrator=${this.is_administrator}
|
||||||
?stay_connected=${this.stay_connected}
|
?stay_connected=${this.stay_connected}
|
||||||
@@ -646,6 +742,7 @@ class TfElement extends LitElement {
|
|||||||
async set_tab(tab) {
|
async set_tab(tab) {
|
||||||
this.tab = tab;
|
this.tab = tab;
|
||||||
if (tab === 'news') {
|
if (tab === 'news') {
|
||||||
|
this.schedule_load_latest();
|
||||||
await tfrpc.rpc.setHash('#');
|
await tfrpc.rpc.setHash('#');
|
||||||
} else if (tab === 'connections') {
|
} else if (tab === 'connections') {
|
||||||
await tfrpc.rpc.setHash('#connections');
|
await tfrpc.rpc.setHash('#connections');
|
||||||
@@ -692,7 +789,7 @@ class TfElement extends LitElement {
|
|||||||
class="w3-bar w3-theme-l1"
|
class="w3-bar w3-theme-l1"
|
||||||
style="position: static; top: 0; z-index: 10"
|
style="position: static; top: 0; z-index: 10"
|
||||||
>
|
>
|
||||||
${this.is_administrator
|
${this.is_administrator && self.tab != 'news'
|
||||||
? html`
|
? html`
|
||||||
<button
|
<button
|
||||||
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
||||||
@@ -751,11 +848,23 @@ class TfElement extends LitElement {
|
|||||||
Loading...
|
Loading...
|
||||||
</div>`
|
</div>`
|
||||||
: this.render_tab();
|
: this.render_tab();
|
||||||
|
let progress =
|
||||||
|
this.progress !== undefined
|
||||||
|
? html`
|
||||||
|
<div style="position: absolute; width: 100%" id="progress">
|
||||||
|
<div
|
||||||
|
class="w3-theme-l3"
|
||||||
|
style=${`height: 4px; position: absolute; right: ${Math.cos(this.progress / 250) > 0 ? 'auto' : '0'}; width: ${50 * Math.sin(this.progress / 250) + 50}%`}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: undefined;
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
||||||
class="w3-theme-dark"
|
class="w3-theme-dark"
|
||||||
>
|
>
|
||||||
|
${progress}
|
||||||
<div style="flex: 0 0">${tabs}</div>
|
<div style="flex: 0 0">${tabs}</div>
|
||||||
<div style="flex: 1 1; overflow: auto; contain: layout">
|
<div style="flex: 1 1; overflow: auto; contain: layout">
|
||||||
${contents}
|
${contents}
|
||||||
|
@@ -16,6 +16,7 @@ class TfComposeElement extends LitElement {
|
|||||||
author: {type: String},
|
author: {type: String},
|
||||||
channel: {type: String},
|
channel: {type: String},
|
||||||
new_thread: {type: Boolean},
|
new_thread: {type: Boolean},
|
||||||
|
recipients: {type: Array},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +92,9 @@ class TfComposeElement extends LitElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true,
|
composed: true,
|
||||||
detail: {
|
detail: {
|
||||||
id: this.branch,
|
id:
|
||||||
|
this.branch ??
|
||||||
|
(this.recipients ? this.recipients.join(',') : undefined),
|
||||||
draft: draft,
|
draft: draft,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -291,7 +294,7 @@ class TfComposeElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated() {
|
get_values() {
|
||||||
let values = Object.entries(this.users).map((x) => ({
|
let values = Object.entries(this.users).map((x) => ({
|
||||||
key: x[1].name ?? x[0],
|
key: x[1].name ?? x[0],
|
||||||
value: x[0],
|
value: x[0],
|
||||||
@@ -307,11 +310,15 @@ class TfComposeElement extends LitElement {
|
|||||||
values
|
values
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
let tribute = new Tribute({
|
let tribute = new Tribute({
|
||||||
iframe: this.shadowRoot,
|
iframe: this.shadowRoot,
|
||||||
collection: [
|
collection: [
|
||||||
{
|
{
|
||||||
values: values,
|
values: this.get_values(),
|
||||||
selectTemplate: function (item) {
|
selectTemplate: function (item) {
|
||||||
return item
|
return item
|
||||||
? `[@${item.original.key}](${item.original.value})`
|
? `[@${item.original.key}](${item.original.value})`
|
||||||
@@ -330,6 +337,7 @@ class TfComposeElement extends LitElement {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
tribute.attach(this.renderRoot.getElementById('edit'));
|
tribute.attach(this.renderRoot.getElementById('edit'));
|
||||||
|
this._tribute = tribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
updated() {
|
updated() {
|
||||||
@@ -340,6 +348,7 @@ class TfComposeElement extends LitElement {
|
|||||||
preview.innerHTML = this.process_text(edit.innerText);
|
preview.innerHTML = this.process_text(edit.innerText);
|
||||||
this.last_updated_text = edit.innerText;
|
this.last_updated_text = edit.innerText;
|
||||||
}
|
}
|
||||||
|
this._tribute.collection[0].values = this.get_values();
|
||||||
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
let tribute = new Tribute({
|
let tribute = new Tribute({
|
||||||
@@ -496,7 +505,17 @@ class TfComposeElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_draft() {
|
get_draft() {
|
||||||
return this.drafts[this.branch || ''] || {};
|
let key =
|
||||||
|
this.branch ||
|
||||||
|
(this.recipients ? this.recipients.join(',') : undefined) ||
|
||||||
|
'';
|
||||||
|
let draft = this.drafts[key] || {};
|
||||||
|
if (this.recipients && !draft.encrypt_to?.length) {
|
||||||
|
draft.encrypt_to = [
|
||||||
|
...new Set(this.recipients).union(new Set(draft.encrypt_to ?? [])),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_encrypt(event) {
|
update_encrypt(event) {
|
||||||
@@ -606,7 +625,7 @@ class TfComposeElement extends LitElement {
|
|||||||
<div class="w3-half">
|
<div class="w3-half">
|
||||||
<span
|
<span
|
||||||
class="w3-input w3-theme-d1 w3-border"
|
class="w3-input w3-theme-d1 w3-border"
|
||||||
style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap"
|
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
||||||
placeholder="Write a post here."
|
placeholder="Write a post here."
|
||||||
id="edit"
|
id="edit"
|
||||||
@input=${this.input}
|
@input=${this.input}
|
||||||
|
@@ -191,12 +191,12 @@ class TfMessageElement extends LitElement {
|
|||||||
div.style.display = 'grid';
|
div.style.display = 'grid';
|
||||||
let img = document.createElement('img');
|
let img = document.createElement('img');
|
||||||
img.src = link;
|
img.src = link;
|
||||||
img.style.maxWidth = '100%';
|
img.style.maxWidth = '100vw';
|
||||||
img.style.maxHeight = '100%';
|
img.style.maxHeight = '100vh';
|
||||||
img.style.display = 'block';
|
img.style.display = 'block';
|
||||||
img.style.margin = 'auto';
|
img.style.margin = 'auto';
|
||||||
img.style.objectFit = 'contain';
|
img.style.objectFit = 'contain';
|
||||||
img.style.width = '100%';
|
img.style.width = '100vw';
|
||||||
div.appendChild(img);
|
div.appendChild(img);
|
||||||
function image_close(event) {
|
function image_close(event) {
|
||||||
document.body.removeChild(div);
|
document.body.removeChild(div);
|
||||||
@@ -537,7 +537,7 @@ class TfMessageElement extends LitElement {
|
|||||||
</style>
|
</style>
|
||||||
<div
|
<div
|
||||||
class="w3-card-4 ${this.class_background()} w3-border-theme w3-margin-top"
|
class="w3-card-4 ${this.class_background()} w3-border-theme w3-margin-top"
|
||||||
style="overflow: auto; overflow-wrap: anywhere; display: block; max-width: 100%"
|
style="overflow-wrap: anywhere; display: block; max-width: 100%"
|
||||||
>
|
>
|
||||||
${inner}
|
${inner}
|
||||||
</div>
|
</div>
|
||||||
@@ -789,60 +789,45 @@ class TfMessageElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
} else if (content.type == 'contact') {
|
} else if (content.type == 'contact') {
|
||||||
return this.render_frame(html`
|
switch (this.format) {
|
||||||
<div class="w3-bar">
|
case 'message':
|
||||||
<div class="w3-bar-item">
|
default:
|
||||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
return this.render_frame(html`
|
||||||
is
|
<div class="w3-bar">
|
||||||
${content.blocking === true
|
<div class="w3-bar-item">
|
||||||
? 'blocking'
|
<tf-user
|
||||||
: content.blocking === false
|
id=${this.message.author}
|
||||||
? 'no longer blocking'
|
.users=${this.users}
|
||||||
: content.following === true
|
></tf-user>
|
||||||
? 'following'
|
is
|
||||||
: content.following === false
|
${content.blocking === true
|
||||||
? 'no longer following'
|
? 'blocking'
|
||||||
: '?'}
|
: content.blocking === false
|
||||||
<tf-user
|
? 'no longer blocking'
|
||||||
id=${this.message.content.contact}
|
: content.following === true
|
||||||
.users=${this.users}
|
? 'following'
|
||||||
></tf-user>
|
: content.following === false
|
||||||
</div>
|
? 'no longer following'
|
||||||
<div class="w3-bar-item w3-right">
|
: '?'}
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
<tf-user
|
||||||
%
|
id=${this.message.content.contact}
|
||||||
</button>
|
.users=${this.users}
|
||||||
<div
|
></tf-user>
|
||||||
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
|
</div>
|
||||||
style="right: 48px"
|
${this.render_menu()} ${this.render_votes()}
|
||||||
>
|
${this.render_actions()}
|
||||||
<a
|
|
||||||
target="_top"
|
|
||||||
class="w3-button w3-bar-item"
|
|
||||||
href=${'#' + encodeURIComponent(this.message?.id)}
|
|
||||||
>View Message</a
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-border-bottom"
|
|
||||||
@click=${this.copy_id}
|
|
||||||
>
|
|
||||||
Copy ID
|
|
||||||
</button>
|
|
||||||
${this.drafts[this.message?.id] === undefined
|
|
||||||
? html`
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item"
|
|
||||||
@click=${this.show_reply}
|
|
||||||
>
|
|
||||||
↩️ Reply
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
</div>
|
</div>
|
||||||
|
`);
|
||||||
|
break;
|
||||||
|
case 'raw':
|
||||||
|
return this.render_frame(html`
|
||||||
|
${this.render_header()}
|
||||||
|
<div class="w3-container">${this.render_raw()}</div>
|
||||||
|
${this.render_votes()} ${this.render_actions()}
|
||||||
</div>
|
</div>
|
||||||
${this.render_votes()} ${this.render_actions()}
|
`);
|
||||||
</div>
|
break;
|
||||||
`);
|
}
|
||||||
} else if (content.type == 'post') {
|
} else if (content.type == 'post') {
|
||||||
let self = this;
|
let self = this;
|
||||||
let body;
|
let body;
|
||||||
|
@@ -14,6 +14,7 @@ class TfProfileElement extends LitElement {
|
|||||||
sequence: {type: Number},
|
sequence: {type: Number},
|
||||||
following: {type: Boolean},
|
following: {type: Boolean},
|
||||||
blocking: {type: Boolean},
|
blocking: {type: Boolean},
|
||||||
|
show_followed: {type: Boolean},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,12 +179,12 @@ class TfProfileElement extends LitElement {
|
|||||||
div.style.display = 'grid';
|
div.style.display = 'grid';
|
||||||
let img = document.createElement('img');
|
let img = document.createElement('img');
|
||||||
img.src = link;
|
img.src = link;
|
||||||
img.style.maxWidth = '100%';
|
img.style.maxWidth = '100vw';
|
||||||
img.style.maxHeight = '100%';
|
img.style.maxHeight = '100vh';
|
||||||
img.style.display = 'block';
|
img.style.display = 'block';
|
||||||
img.style.margin = 'auto';
|
img.style.margin = 'auto';
|
||||||
img.style.objectFit = 'contain';
|
img.style.objectFit = 'contain';
|
||||||
img.style.width = '100%';
|
img.style.width = '100vw';
|
||||||
div.appendChild(img);
|
div.appendChild(img);
|
||||||
function image_close(event) {
|
function image_close(event) {
|
||||||
document.body.removeChild(div);
|
document.body.removeChild(div);
|
||||||
@@ -202,11 +203,7 @@ class TfProfileElement extends LitElement {
|
|||||||
|
|
||||||
toggle_account_list(event) {
|
toggle_account_list(event) {
|
||||||
let content = event.srcElement.nextElementSibling;
|
let content = event.srcElement.nextElementSibling;
|
||||||
if (content.classList.toggle('w3-hide')) {
|
this.show_followed = !this.show_followed;
|
||||||
event.srcElement.innerText = 'Show Followed Accounts';
|
|
||||||
} else {
|
|
||||||
event.srcElement.innerText = 'Hide Followed Accounts';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load_follows() {
|
async load_follows() {
|
||||||
@@ -214,12 +211,13 @@ class TfProfileElement extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="w3-container">
|
<div class="w3-container">
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-block w3-theme-d1"
|
class="w3-button w3-block w3-theme-d1 followed_accounts"
|
||||||
@click=${this.toggle_account_list}
|
@click=${this.toggle_account_list}
|
||||||
>
|
>
|
||||||
Show Followed Accounts
|
${this.show_followed ? 'Hide' : 'Show'} Followed Accounts
|
||||||
|
(${Object.keys(accounts).length})
|
||||||
</button>
|
</button>
|
||||||
<div class="w3-hide w3-card">
|
<div class=${'w3-card' + (this.show_followed ? '' : ' w3-hide')}>
|
||||||
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
||||||
${Object.keys(accounts).map(
|
${Object.keys(accounts).map(
|
||||||
(x) => html`
|
(x) => html`
|
||||||
@@ -329,7 +327,7 @@ class TfProfileElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-direction: row; gap: 1em">
|
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||||
${edit_profile}
|
${edit_profile}
|
||||||
<div style="flex: 1 0 50%">
|
<div style="flex: 1 0 50%; contain: layout; overflow: auto; word-wrap: normal; word-break: normal">
|
||||||
${
|
${
|
||||||
image
|
image
|
||||||
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
||||||
@@ -351,6 +349,9 @@ class TfProfileElement extends LitElement {
|
|||||||
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
|
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
|
||||||
<footer class="w3-container">
|
<footer class="w3-container">
|
||||||
<p>
|
<p>
|
||||||
|
<a class="w3-button w3-theme-d1" href=${'#🔐' + (this.id != this.whoami ? this.id : '')}>
|
||||||
|
Open Private Chat
|
||||||
|
</a>
|
||||||
${edit}
|
${edit}
|
||||||
${follow}
|
${follow}
|
||||||
${block}
|
${block}
|
||||||
|
@@ -43,6 +43,8 @@ const tf = css`
|
|||||||
border-left: 4px solid #fff;
|
border-left: 4px solid #fff;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
time_range: {type: Array},
|
time_range: {type: Array},
|
||||||
time_loading: {type: Array},
|
time_loading: {type: Array},
|
||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
|
grouped_private_messages: {type: Object},
|
||||||
recent_reactions: {type: Array},
|
recent_reactions: {type: Array},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -106,8 +107,15 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fetch_messages(start_time, end_time) {
|
async fetch_messages(start_time, end_time) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('loadmessages', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
this.time_loading = [start_time, end_time];
|
this.time_loading = [start_time, end_time];
|
||||||
let result;
|
let result;
|
||||||
|
const k_max_results = 64;
|
||||||
if (this.hash == '#@') {
|
if (this.hash == '#@') {
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
@@ -118,7 +126,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
WHERE
|
WHERE
|
||||||
messages.author != ?1 AND
|
messages.author != ?1 AND
|
||||||
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
||||||
ORDER BY timestamp DESC limit 20)
|
ORDER BY timestamp DESC limit ?5)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM mentions
|
FROM mentions
|
||||||
JOIN messages_refs ON mentions.id = messages_refs.ref
|
JOIN messages_refs ON mentions.id = messages_refs.ref
|
||||||
@@ -131,6 +139,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
JSON.stringify(this.following),
|
JSON.stringify(this.following),
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
|
k_max_results,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('#@')) {
|
} else if (this.hash.startsWith('#@')) {
|
||||||
@@ -140,7 +149,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
||||||
ORDER BY sequence DESC LIMIT 20
|
ORDER BY sequence DESC LIMIT ?4
|
||||||
)
|
)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM selected
|
FROM selected
|
||||||
@@ -149,7 +158,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
UNION
|
UNION
|
||||||
SELECT TRUE AS is_primary, * FROM selected
|
SELECT TRUE AS is_primary, * FROM selected
|
||||||
`,
|
`,
|
||||||
[this.hash.substring(1), start_time, end_time]
|
[this.hash.substring(1), start_time, end_time, k_max_results]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('#%')) {
|
} else if (this.hash.startsWith('#%')) {
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
@@ -184,13 +193,14 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
)
|
)
|
||||||
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
||||||
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
||||||
ORDER BY all_news.timestamp DESC LIMIT 20
|
ORDER BY all_news.timestamp DESC LIMIT ?5
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
JSON.stringify(this.following),
|
JSON.stringify(this.following),
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
this.hash.substring(2),
|
this.hash.substring(2),
|
||||||
|
k_max_results,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
let t1 = new Date();
|
let t1 = new Date();
|
||||||
@@ -199,7 +209,9 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
console.log(
|
console.log(
|
||||||
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
|
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
|
||||||
);
|
);
|
||||||
} else if (this.hash == '#🔐') {
|
} else if (this.hash.startsWith('#🔐')) {
|
||||||
|
let ids =
|
||||||
|
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
@@ -208,9 +220,18 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
WHERE
|
WHERE
|
||||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||||
json(messages.content) LIKE '"%'
|
json(messages.content) LIKE '"%'
|
||||||
ORDER BY messages.rowid DESC LIMIT 20
|
ORDER BY messages.rowid DESC LIMIT ?4
|
||||||
`,
|
`,
|
||||||
[JSON.stringify(this.private_messages), start_time, end_time]
|
[
|
||||||
|
JSON.stringify(
|
||||||
|
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
|
||||||
|
(x) => x.id
|
||||||
|
) ?? []
|
||||||
|
),
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
k_max_results,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||||
} else if (this.hash == '#👍') {
|
} else if (this.hash == '#👍') {
|
||||||
@@ -222,14 +243,14 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
WHERE
|
WHERE
|
||||||
messages.content ->> 'type' = 'vote' AND
|
messages.content ->> 'type' = 'vote' AND
|
||||||
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
||||||
ORDER BY timestamp DESC limit 20)
|
ORDER BY timestamp DESC limit ?4)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM votes
|
FROM votes
|
||||||
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
||||||
UNION
|
UNION
|
||||||
SELECT TRUE AS is_primary, * FROM votes
|
SELECT TRUE AS is_primary, * FROM votes
|
||||||
`,
|
`,
|
||||||
[JSON.stringify(this.following), start_time, end_time]
|
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let t0 = new Date();
|
let t0 = new Date();
|
||||||
@@ -240,9 +261,9 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
JOIN json_each(?) AS following ON messages.author = following.value
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||||
messages.content ->> 'type' != 'vote'
|
messages.content ->> 'type' != 'vote'
|
||||||
ORDER BY timestamp DESC LIMIT 20
|
ORDER BY timestamp DESC LIMIT ?4
|
||||||
`,
|
`,
|
||||||
[JSON.stringify(this.following), start_time, end_time]
|
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||||
);
|
);
|
||||||
let t1 = new Date();
|
let t1 = new Date();
|
||||||
result = await this._fetch_related_messages(initial_messages);
|
result = await this._fetch_related_messages(initial_messages);
|
||||||
@@ -365,12 +386,16 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
let self = this;
|
let self = this;
|
||||||
this.loading++;
|
this.loading++;
|
||||||
let messages = [];
|
let messages = [];
|
||||||
|
let original_hash = this.hash;
|
||||||
try {
|
try {
|
||||||
if (this._messages_hash !== this.hash) {
|
if (this._messages_hash !== this.hash) {
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
this._messages_hash = this.hash;
|
this._messages_hash = this.hash;
|
||||||
}
|
}
|
||||||
this._messages_following = this.following;
|
this._messages_following = JSON.stringify(this.following);
|
||||||
|
this._private_messages =
|
||||||
|
JSON.stringify(this.private_messages) +
|
||||||
|
JSON.stringify(this.grouped_private_messages);
|
||||||
let now = new Date().valueOf();
|
let now = new Date().valueOf();
|
||||||
let start_time = now - 24 * 60 * 60 * 1000;
|
let start_time = now - 24 * 60 * 60 * 1000;
|
||||||
this.start_time = start_time;
|
this.start_time = start_time;
|
||||||
@@ -383,7 +408,9 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
} finally {
|
} finally {
|
||||||
this.loading--;
|
this.loading--;
|
||||||
}
|
}
|
||||||
this.messages = this.merge_messages(this.messages, messages);
|
if (this.hash == original_hash) {
|
||||||
|
this.messages = this.merge_messages(this.messages, messages);
|
||||||
|
}
|
||||||
this.time_loading = undefined;
|
this.time_loading = undefined;
|
||||||
console.log(
|
console.log(
|
||||||
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
|
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
|
||||||
@@ -409,12 +436,42 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close_private_chat() {
|
||||||
|
this.mark_all_read();
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('closeprivatechat', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: {
|
||||||
|
key: JSON.stringify(
|
||||||
|
this.hash == '#🔐'
|
||||||
|
? []
|
||||||
|
: this.hash.substring('#🔐'.length).split(',')
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
tfrpc.rpc.setHash('#');
|
||||||
|
}
|
||||||
|
|
||||||
|
render_close_chat_button() {
|
||||||
|
if (this.hash.startsWith('#🔐')) {
|
||||||
|
return html`
|
||||||
|
<button class="w3-button w3-theme-d1" @click=${this.close_private_chat}>
|
||||||
|
Close Chat
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (
|
if (
|
||||||
!this.messages ||
|
!this.messages ||
|
||||||
this._messages_hash !== this.hash ||
|
this._messages_hash !== this.hash ||
|
||||||
JSON.stringify(this._messages_following) !==
|
this._messages_following !== JSON.stringify(this.following) ||
|
||||||
JSON.stringify(this.following)
|
this._private_messages !==
|
||||||
|
JSON.stringify(this.private_messages) +
|
||||||
|
JSON.stringify(this.grouped_private_messages)
|
||||||
) {
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
`loading messages for ${this.whoami} (following ${this.following.length})`
|
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||||
@@ -474,6 +531,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
Mark All Read
|
Mark All Read
|
||||||
</button>`
|
</button>`
|
||||||
: undefined}
|
: undefined}
|
||||||
|
${this.render_close_chat_button()}
|
||||||
<tf-news
|
<tf-news
|
||||||
id="news"
|
id="news"
|
||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
|
@@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
channels_latest: {type: Object},
|
channels_latest: {type: Object},
|
||||||
connections: {type: Array},
|
connections: {type: Array},
|
||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
|
grouped_private_messages: {type: Object},
|
||||||
recent_reactions: {type: Array},
|
recent_reactions: {type: Array},
|
||||||
peer_exchange: {type: Boolean},
|
peer_exchange: {type: Boolean},
|
||||||
is_administrator: {type: Boolean},
|
is_administrator: {type: Boolean},
|
||||||
@@ -115,6 +116,19 @@ class TfTabNewsElement extends LitElement {
|
|||||||
) {
|
) {
|
||||||
return '✉️ ';
|
return '✉️ ';
|
||||||
}
|
}
|
||||||
|
} else if (channel?.startsWith('🔐')) {
|
||||||
|
let key = JSON.stringify(channel.substring('🔐'.length).split(','));
|
||||||
|
if (this.grouped_private_messages?.[key]) {
|
||||||
|
let grouped_latest = Math.max(
|
||||||
|
...this.grouped_private_messages?.[key]?.map((x) => x.rowid)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
this.channels_unread[channel] === undefined ||
|
||||||
|
grouped_latest > this.channels_unread[channel]
|
||||||
|
) {
|
||||||
|
return '✉️ ';
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
this.channels_latest[channel] &&
|
this.channels_latest[channel] &&
|
||||||
this.channels_latest[channel] > 0 &&
|
this.channels_latest[channel] > 0 &&
|
||||||
@@ -156,11 +170,8 @@ class TfTabNewsElement extends LitElement {
|
|||||||
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
compare_follows() {
|
compare_follows(a, b) {
|
||||||
const now = new Date().valueOf();
|
return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0;
|
||||||
return function (a, b) {
|
|
||||||
return (b[1].ts > now ? -1 : b[1].ts) - (a[1].ts > now ? -1 : a[1].ts);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suggested_follows() {
|
suggested_follows() {
|
||||||
@@ -169,9 +180,11 @@ class TfTabNewsElement extends LitElement {
|
|||||||
** pinned at the top.
|
** pinned at the top.
|
||||||
*/
|
*/
|
||||||
let self = this;
|
let self = this;
|
||||||
|
let now = new Date().valueOf();
|
||||||
return Object.entries(this.users)
|
return Object.entries(this.users)
|
||||||
|
.filter((x) => x[1].ts < now)
|
||||||
.filter((x) => x[1].follow_depth > 1)
|
.filter((x) => x[1].follow_depth > 1)
|
||||||
.sort(self.compare_follows())
|
.sort(self.compare_follows)
|
||||||
.slice(0, 8)
|
.slice(0, 8)
|
||||||
.map((x) => x[0]);
|
.map((x) => x[0]);
|
||||||
}
|
}
|
||||||
@@ -181,6 +194,10 @@ class TfTabNewsElement extends LitElement {
|
|||||||
await this.check_peer_exchange();
|
await this.check_peer_exchange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_loading() {
|
||||||
|
return this.shadowRoot?.getElementById('news')?.loading;
|
||||||
|
}
|
||||||
|
|
||||||
render_sidebar() {
|
render_sidebar() {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@@ -203,7 +220,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
new Event('refresh', {bubbles: true, composed: true})
|
new Event('refresh', {bubbles: true, composed: true})
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span style="width: 1.5em; height: 1.5em; padding: 8px">↻</span>
|
<span style="display: inline-block; width: 1.8em">↻</span>
|
||||||
Sync now
|
Sync now
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -216,7 +233,10 @@ class TfTabNewsElement extends LitElement {
|
|||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
${this.stay_connected ? '🔗 Online mode' : '⛓️💥 Passive mode'}
|
<span style="display: inline-block; width: 1.8em"
|
||||||
|
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
||||||
|
>
|
||||||
|
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: undefined}
|
: undefined}
|
||||||
@@ -251,12 +271,29 @@ class TfTabNewsElement extends LitElement {
|
|||||||
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
|
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
|
||||||
>${this.unread_status('👍')}👍votes</a
|
>${this.unread_status('👍')}👍votes</a
|
||||||
>
|
>
|
||||||
<a
|
${Object.keys(this?.grouped_private_messages ?? [])
|
||||||
href="#🔐"
|
?.sort()
|
||||||
class="w3-bar-item w3-button"
|
?.map(
|
||||||
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
|
(key) => html`
|
||||||
>${this.unread_status('🔐')}🔐private</a
|
<a
|
||||||
>
|
href=${'#🔐' + JSON.parse(key).join(',')}
|
||||||
|
class="w3-bar-item w3-button"
|
||||||
|
style=${this.hash == '#🔐' + JSON.parse(key).join(',')
|
||||||
|
? 'font-weight: bold'
|
||||||
|
: undefined}
|
||||||
|
>${this.unread_status('🔐' + JSON.parse(key).join(','))}
|
||||||
|
${(key != '[]' ? JSON.parse(key) : [this.whoami]).map(
|
||||||
|
(id) => html`
|
||||||
|
<tf-user
|
||||||
|
id=${id}
|
||||||
|
nolink="true"
|
||||||
|
.users=${this.users}
|
||||||
|
></tf-user>
|
||||||
|
`
|
||||||
|
)}</a
|
||||||
|
>
|
||||||
|
`
|
||||||
|
)}
|
||||||
${Object.keys(this.drafts)
|
${Object.keys(this.drafts)
|
||||||
.sort()
|
.sort()
|
||||||
.map(
|
.map(
|
||||||
@@ -374,7 +411,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
return cache(html`
|
return cache(html`
|
||||||
${this.render_sidebar()}
|
${this.render_sidebar()}
|
||||||
<div
|
<div
|
||||||
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto; contain: layout"
|
style="margin-left: 2in; padding: 0px; top: 0; height: 100vh; max-height: 100%; overflow: auto; contain: layout"
|
||||||
id="main"
|
id="main"
|
||||||
class="w3-main"
|
class="w3-main"
|
||||||
>
|
>
|
||||||
@@ -412,6 +449,9 @@ class TfTabNewsElement extends LitElement {
|
|||||||
.drafts=${this.drafts}
|
.drafts=${this.drafts}
|
||||||
@tf-draft=${this.draft}
|
@tf-draft=${this.draft}
|
||||||
.channel=${this.channel()}
|
.channel=${this.channel()}
|
||||||
|
.recipients=${this.hash.startsWith('#🔐')
|
||||||
|
? this.hash.substring('#🔐'.length).split(',')
|
||||||
|
: undefined}
|
||||||
></tf-compose>
|
></tf-compose>
|
||||||
</div>
|
</div>
|
||||||
${profile}
|
${profile}
|
||||||
@@ -428,6 +468,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
.channels_unread=${this.channels_unread}
|
.channels_unread=${this.channels_unread}
|
||||||
.channels_latest=${this.channels_latest}
|
.channels_latest=${this.channels_latest}
|
||||||
.private_messages=${this.private_messages}
|
.private_messages=${this.private_messages}
|
||||||
|
.grouped_private_messages=${this.grouped_private_messages}
|
||||||
.recent_reactions=${this.recent_reactions}
|
.recent_reactions=${this.recent_reactions}
|
||||||
></tf-tab-news-feed>
|
></tf-tab-news-feed>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -9,6 +9,7 @@ class TfUserElement extends LitElement {
|
|||||||
fallback_name: {type: String},
|
fallback_name: {type: String},
|
||||||
icon_only: {type: Boolean},
|
icon_only: {type: Boolean},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
|
nolink: {type: Boolean},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +38,9 @@ class TfUserElement extends LitElement {
|
|||||||
let name_string = name ?? this.fallback_name ?? this.id;
|
let name_string = name ?? this.fallback_name ?? this.id;
|
||||||
name = this.icon_only
|
name = this.icon_only
|
||||||
? undefined
|
? undefined
|
||||||
: html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`;
|
: !this.nolink
|
||||||
|
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
|
||||||
|
: html`<span>${name_string}</span>`;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
let image_link = user.image;
|
let image_link = user.image;
|
||||||
@@ -56,7 +59,8 @@ class TfUserElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html` <div
|
return html` <div
|
||||||
style="display: inline-block; vertical-align: middle; font-weight: bold; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis"
|
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
|
||||||
|
(this.nolink ? '' : '; font-weight: bold')}
|
||||||
>
|
>
|
||||||
${image} ${name}
|
${image} ${name}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "👋",
|
"emoji": "👋",
|
||||||
"previous": "&3puDxDNnf6C+YXpFysYLgxFMAy54/AO9V7Xpja6qO/k=.sha256"
|
"previous": "&5NkMRSgcMqCYF3xcLOBmaytkoxfV9zx4br7JladKPTs=.sha256"
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
async function main() {
|
|
||||||
await app.setDocument(utf8Decode(getFile('index.html')));
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
1
apps/welcome/gitea.svg
Normal file
1
apps/welcome/gitea.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
@@ -47,8 +47,10 @@
|
|||||||
<a
|
<a
|
||||||
class="w3-button w3-black w3-padding-large"
|
class="w3-button w3-black w3-padding-large"
|
||||||
href="https://dev.tildefriends.net/cory/tildefriends"
|
href="https://dev.tildefriends.net/cory/tildefriends"
|
||||||
><i class="fa fa-mug-hot"></i> Development</a
|
|
||||||
>
|
>
|
||||||
|
<img src="gitea.svg" style="height: 1em; margin: 0" />
|
||||||
|
Development
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
class="w3-button w3-black w3-padding-large"
|
class="w3-button w3-black w3-padding-large"
|
||||||
href="https://docs.tildefriends.net/"
|
href="https://docs.tildefriends.net/"
|
||||||
|
8
apps/wiki/lit-all.min.js
vendored
8
apps/wiki/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
37
core/app.js
37
core/app.js
@@ -1,7 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \defgroup tfapp Tilde Friends App JS
|
||||||
|
* Tilde Friends server-side app wrapper.
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \cond */
|
||||||
import * as core from './core.js';
|
import * as core from './core.js';
|
||||||
|
|
||||||
let gSessionIndex = 0;
|
export {App};
|
||||||
|
/** \endcond */
|
||||||
|
|
||||||
|
/** A sequence number of apps. */
|
||||||
|
let g_session_index = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App constructor.
|
||||||
|
** @return An app instance.
|
||||||
|
*/
|
||||||
function App() {
|
function App() {
|
||||||
this._send_queue = [];
|
this._send_queue = [];
|
||||||
this.calls = {};
|
this.calls = {};
|
||||||
@@ -9,6 +25,12 @@ function App() {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Create a function wrapper that when called invokes a function on the app
|
||||||
|
** itself.
|
||||||
|
** @param api The function and argument names.
|
||||||
|
** @return A function.
|
||||||
|
*/
|
||||||
App.prototype.makeFunction = function (api) {
|
App.prototype.makeFunction = function (api) {
|
||||||
let self = this;
|
let self = this;
|
||||||
let result = function () {
|
let result = function () {
|
||||||
@@ -32,6 +54,10 @@ App.prototype.makeFunction = function (api) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Send a message to the app.
|
||||||
|
** @param message The message to send.
|
||||||
|
*/
|
||||||
App.prototype.send = function (message) {
|
App.prototype.send = function (message) {
|
||||||
if (this._send_queue) {
|
if (this._send_queue) {
|
||||||
if (this._on_output) {
|
if (this._on_output) {
|
||||||
@@ -46,6 +72,11 @@ App.prototype.send = function (message) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App socket handler.
|
||||||
|
** @param request The HTTP request of the WebSocket connection.
|
||||||
|
** @param response The HTTP response.
|
||||||
|
*/
|
||||||
exports.app_socket = async function socket(request, response) {
|
exports.app_socket = async function socket(request, response) {
|
||||||
let process;
|
let process;
|
||||||
let options = {};
|
let options = {};
|
||||||
@@ -133,7 +164,7 @@ exports.app_socket = async function socket(request, response) {
|
|||||||
options.packageOwner = packageOwner;
|
options.packageOwner = packageOwner;
|
||||||
options.packageName = packageName;
|
options.packageName = packageName;
|
||||||
options.url = message.url;
|
options.url = message.url;
|
||||||
let sessionId = 'session_' + (gSessionIndex++).toString();
|
let sessionId = 'session_' + (g_session_index++).toString();
|
||||||
if (blobId) {
|
if (blobId) {
|
||||||
if (message.edit_only) {
|
if (message.edit_only) {
|
||||||
response.send(
|
response.send(
|
||||||
@@ -218,4 +249,4 @@ exports.app_socket = async function socket(request, response) {
|
|||||||
response.upgrade(100, {});
|
response.upgrade(100, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export {App};
|
/** @} */
|
||||||
|
411
core/client.js
411
core/client.js
@@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \defgroup tfclient Tilde Friends Client JS
|
||||||
|
* Tilde Friends client-side browser JavaScript.
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \cond */
|
||||||
import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
|
import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
|
||||||
|
|
||||||
let cm6;
|
let cm6;
|
||||||
@@ -13,8 +21,9 @@ let gUnloading;
|
|||||||
let kErrorColor = '#dc322f';
|
let kErrorColor = '#dc322f';
|
||||||
let kDisconnectColor = '#f00';
|
let kDisconnectColor = '#f00';
|
||||||
let kStatusColor = '#fff';
|
let kStatusColor = '#fff';
|
||||||
|
/** \endcond */
|
||||||
|
|
||||||
// Functions that server-side app code can call through the app object.
|
/** Functions that server-side app code can call through the app object. */
|
||||||
const k_api = {
|
const k_api = {
|
||||||
setDocument: {args: ['content'], func: api_setDocument},
|
setDocument: {args: ['content'], func: api_setDocument},
|
||||||
postMessage: {args: ['message'], func: api_postMessage},
|
postMessage: {args: ['message'], func: api_postMessage},
|
||||||
@@ -26,29 +35,14 @@ const k_api = {
|
|||||||
setHash: {args: ['hash'], func: api_setHash},
|
setHash: {args: ['hash'], func: api_setHash},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(tasiaiso): this is only used once, move it down ?
|
|
||||||
const k_global_style = css`
|
|
||||||
a:link {
|
|
||||||
color: #268bd2;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
color: #6c71c4;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #859900;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:active {
|
|
||||||
color: #2aa198;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that represents the top bar
|
* Class that represents the top bar
|
||||||
*/
|
*/
|
||||||
class TfNavigationElement extends LitElement {
|
class TfNavigationElement extends LitElement {
|
||||||
|
/**
|
||||||
|
* Get Lit Html properties.
|
||||||
|
* @return The properties.
|
||||||
|
*/
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
credentials: {type: Object},
|
credentials: {type: Object},
|
||||||
@@ -64,6 +58,9 @@ class TfNavigationElement extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TfNavigationElement instance.
|
||||||
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.permissions = {};
|
this.permissions = {};
|
||||||
@@ -75,8 +72,8 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Toggle editor visibility.
|
||||||
* @param {*} event
|
* @param event The HTML event.
|
||||||
*/
|
*/
|
||||||
toggle_edit(event) {
|
toggle_edit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -88,18 +85,18 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Remove a stored permission.
|
||||||
* @param {*} key
|
* @param key The permission to reset.
|
||||||
*/
|
*/
|
||||||
reset_permission(key) {
|
reset_permission(key) {
|
||||||
send({action: 'resetPermission', permission: key});
|
send({action: 'resetPermission', permission: key});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Get or create a spark line.
|
||||||
* @param {*} key
|
* @param key The spark line identifier.
|
||||||
* @param {*} options
|
* @param options Spark line options.
|
||||||
* @returns
|
* @return A spark line HTML element.
|
||||||
*/
|
*/
|
||||||
get_spark_line(key, options) {
|
get_spark_line(key, options) {
|
||||||
if (!this.spark_lines[key]) {
|
if (!this.spark_lines[key]) {
|
||||||
@@ -118,29 +115,49 @@ class TfNavigationElement extends LitElement {
|
|||||||
return this.spark_lines[key];
|
return this.spark_lines[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active SSB identity for the current application.
|
||||||
|
* @param id The identity.
|
||||||
|
*/
|
||||||
set_active_identity(id) {
|
set_active_identity(id) {
|
||||||
send({action: 'setActiveIdentity', identity: id});
|
send({action: 'setActiveIdentity', identity: id});
|
||||||
this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show');
|
this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show');
|
||||||
}
|
}
|
||||||
|
|
||||||
create_identity(event) {
|
/**
|
||||||
|
* Create a new SSB identity.
|
||||||
|
*/
|
||||||
|
create_identity() {
|
||||||
if (confirm('Are you sure you want to create a new identity?')) {
|
if (confirm('Are you sure you want to create a new identity?')) {
|
||||||
send({action: 'createIdentity'});
|
send({action: 'createIdentity'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle visibility of the ID dropdown.
|
||||||
|
*/
|
||||||
toggle_id_dropdown() {
|
toggle_id_dropdown() {
|
||||||
this.renderRoot.getElementById('id_dropdown').classList.toggle('w3-show');
|
this.renderRoot.getElementById('id_dropdown').classList.toggle('w3-show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the current identity's SSB profile.
|
||||||
|
*/
|
||||||
edit_profile() {
|
edit_profile() {
|
||||||
window.location.href = '/~core/ssb/#' + this.identity;
|
window.location.href = '/~core/ssb/#' + this.identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign out of the current Tilde Friends user.
|
||||||
|
*/
|
||||||
logout() {
|
logout() {
|
||||||
window.location.href = `/login/logout?return=${encodeURIComponent(url() + hash())}`;
|
window.location.href = `/login/logout?return=${encodeURIComponent(url() + hash())}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the identity dropdown.
|
||||||
|
* @return Lit HTML.
|
||||||
|
*/
|
||||||
render_identity() {
|
render_identity() {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
@@ -245,8 +262,8 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the permissions popup.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render_permissions() {
|
render_permissions() {
|
||||||
if (this.show_permissions) {
|
if (this.show_permissions) {
|
||||||
@@ -287,16 +304,36 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the current error.
|
||||||
|
*/
|
||||||
clear_error() {
|
clear_error() {
|
||||||
this.status = {};
|
this.status = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the navigation bar.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
const k_global_style = css`
|
||||||
|
a:link {
|
||||||
|
color: #268bd2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #6c71c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #859900;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active {
|
||||||
|
color: #2aa198;
|
||||||
|
}
|
||||||
|
`;
|
||||||
return html`
|
return html`
|
||||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||||
<style>
|
<style>
|
||||||
@@ -401,12 +438,19 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tf-navigation element.
|
||||||
|
*/
|
||||||
customElements.define('tf-navigation', TfNavigationElement);
|
customElements.define('tf-navigation', TfNavigationElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* A file in the files sidebar.
|
||||||
*/
|
*/
|
||||||
class TfFilesElement extends LitElement {
|
class TfFilesElement extends LitElement {
|
||||||
|
/**
|
||||||
|
* LitElement properties.
|
||||||
|
* @return The properties.
|
||||||
|
*/
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
current: {type: String},
|
current: {type: String},
|
||||||
@@ -416,6 +460,9 @@ class TfFilesElement extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TfFilesElement instance.
|
||||||
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.files = {};
|
this.files = {};
|
||||||
@@ -423,8 +470,8 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Select a clicked file.
|
||||||
* @param {*} file
|
* @param file The file.
|
||||||
*/
|
*/
|
||||||
file_click(file) {
|
file_click(file) {
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
@@ -439,9 +486,9 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render a single file in the file list.
|
||||||
* @param {*} file
|
* @param file The file.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render_file(file) {
|
render_file(file) {
|
||||||
let classes = ['file'];
|
let classes = ['file'];
|
||||||
@@ -463,8 +510,8 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Create a file entry for a dropped file.
|
||||||
* @param {*} event
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
async drop(event) {
|
async drop(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -489,8 +536,8 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Called when a file starts being dragged over the file.
|
||||||
* @param {*} event
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
drag_enter(event) {
|
drag_enter(event) {
|
||||||
this.dropping++;
|
this.dropping++;
|
||||||
@@ -499,8 +546,8 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Called when a file stops being dragged over the file.
|
||||||
* @param {*} event
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
drag_leave(event) {
|
drag_leave(event) {
|
||||||
this.dropping--;
|
this.dropping--;
|
||||||
@@ -509,13 +556,17 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a file is being dragged over the file.
|
||||||
|
* @param event The event.
|
||||||
|
*/
|
||||||
drag_over(event) {
|
drag_over(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the file.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
@@ -562,9 +613,13 @@ class TfFilesElement extends LitElement {
|
|||||||
customElements.define('tf-files', TfFilesElement);
|
customElements.define('tf-files', TfFilesElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* The files pane element.
|
||||||
*/
|
*/
|
||||||
class TfFilesPaneElement extends LitElement {
|
class TfFilesPaneElement extends LitElement {
|
||||||
|
/**
|
||||||
|
* Get Lit Html properties.
|
||||||
|
* @return The properties.
|
||||||
|
*/
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
expanded: {type: Boolean},
|
expanded: {type: Boolean},
|
||||||
@@ -573,6 +628,9 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TfFilesPaneElement instance.
|
||||||
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.expanded = window.localStorage.getItem('files') != '0';
|
this.expanded = window.localStorage.getItem('files') != '0';
|
||||||
@@ -580,8 +638,8 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Set whether the files pane is expanded.
|
||||||
* @param {*} expanded
|
* @param expanded Whether the files pane is expanded.
|
||||||
*/
|
*/
|
||||||
set_expanded(expanded) {
|
set_expanded(expanded) {
|
||||||
this.expanded = expanded;
|
this.expanded = expanded;
|
||||||
@@ -589,8 +647,8 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the files pane element.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
@@ -649,7 +707,7 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
customElements.define('tf-files-pane', TfFilesPaneElement);
|
customElements.define('tf-files-pane', TfFilesPaneElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* A tiny graph.
|
||||||
*/
|
*/
|
||||||
class TfSparkLineElement extends LitElement {
|
class TfSparkLineElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -669,9 +727,9 @@ class TfSparkLineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Add a data point to the graph.
|
||||||
* @param {*} key
|
* @param key The line to which the point applies.
|
||||||
* @param {*} value
|
* @param value The numeric value of the data point.
|
||||||
*/
|
*/
|
||||||
append(key, value) {
|
append(key, value) {
|
||||||
let line = null;
|
let line = null;
|
||||||
@@ -698,9 +756,9 @@ class TfSparkLineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render a single series line.
|
||||||
* @param {*} line
|
* @param line The line data.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render_line(line) {
|
render_line(line) {
|
||||||
if (line?.values?.length >= 2) {
|
if (line?.values?.length >= 2) {
|
||||||
@@ -716,8 +774,8 @@ class TfSparkLineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the graph.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let max =
|
let max =
|
||||||
@@ -744,7 +802,9 @@ class TfSparkLineElement extends LitElement {
|
|||||||
|
|
||||||
customElements.define('tf-sparkline', TfSparkLineElement);
|
customElements.define('tf-sparkline', TfSparkLineElement);
|
||||||
|
|
||||||
// TODOC
|
/**
|
||||||
|
* A keyboard key is pressed down.
|
||||||
|
*/
|
||||||
window.addEventListener('keydown', function (event) {
|
window.addEventListener('keydown', function (event) {
|
||||||
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
@@ -760,10 +820,9 @@ window.addEventListener('keydown', function (event) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Make sure a set of dependencies are loaded
|
||||||
* @param {*} nodes
|
* @param nodes An array of descriptions of dependencies to load.
|
||||||
* @param {*} callback
|
* @param callback Called when all dependencies are loaded.
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
function ensureLoaded(nodes, callback) {
|
function ensureLoaded(nodes, callback) {
|
||||||
if (!nodes.length) {
|
if (!nodes.length) {
|
||||||
@@ -806,24 +865,23 @@ function ensureLoaded(nodes, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Check whether the editior is currently visible.
|
||||||
* @returns
|
* @return true if the editor is visible.
|
||||||
*/
|
*/
|
||||||
function editing() {
|
function editing() {
|
||||||
return document.getElementById('editPane').style.display != 'none';
|
return document.getElementById('editPane').style.display != 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Check whether only the editor is visible and the app is hidden.
|
||||||
* @returns
|
* @return true if the editor is visible and the app is not.
|
||||||
*/
|
*/
|
||||||
function is_edit_only() {
|
function is_edit_only() {
|
||||||
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Show the editor.
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
async function edit() {
|
async function edit() {
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
@@ -850,30 +908,17 @@ async function edit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Open a performance trace.
|
||||||
*/
|
*/
|
||||||
function trace() {
|
function trace() {
|
||||||
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Load a single file.
|
||||||
* @param {*} name
|
* @param name The name by which the file is known.
|
||||||
* @returns
|
* @param id The file's ID.
|
||||||
*/
|
* @return A promise resolved with the file's contents.
|
||||||
function guessMode(name) {
|
|
||||||
return name.endsWith('.js')
|
|
||||||
? 'javascript'
|
|
||||||
: name.endsWith('.html')
|
|
||||||
? 'htmlmixed'
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
* @param {*} name
|
|
||||||
* @param {*} id
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
function loadFile(name, id) {
|
function loadFile(name, id) {
|
||||||
return fetch('/' + id + '/view')
|
return fetch('/' + id + '/view')
|
||||||
@@ -899,9 +944,9 @@ function loadFile(name, id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Load files for the app.
|
||||||
* @param {*} path
|
* @param path The app path to load.
|
||||||
* @returns
|
* @return A promise resolved when the app is laoded.
|
||||||
*/
|
*/
|
||||||
async function load(path) {
|
async function load(path) {
|
||||||
let response = await fetch((path || url()) + 'view');
|
let response = await fetch((path || url()) + 'view');
|
||||||
@@ -941,7 +986,7 @@ async function load(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Hide the editor.
|
||||||
*/
|
*/
|
||||||
function closeEditor() {
|
function closeEditor() {
|
||||||
window.localStorage.setItem('editing', '0');
|
window.localStorage.setItem('editing', '0');
|
||||||
@@ -950,17 +995,9 @@ function closeEditor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Save the app.
|
||||||
* @returns
|
* @param save_to An optional path to which to save the app.
|
||||||
*/
|
* @return A promise resoled when the app is saved.
|
||||||
function explodePath() {
|
|
||||||
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
* @param {*} save_to
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
function save(save_to) {
|
function save(save_to) {
|
||||||
document.getElementById('save').disabled = true;
|
document.getElementById('save').disabled = true;
|
||||||
@@ -1050,6 +1087,8 @@ function save(save_to) {
|
|||||||
|
|
||||||
if (save_path != window.location.pathname) {
|
if (save_path != window.location.pathname) {
|
||||||
alert('Saved to ' + save_path + '.');
|
alert('Saved to ' + save_path + '.');
|
||||||
|
} else if (!gFiles['app.js']) {
|
||||||
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
reconnect(save_path);
|
reconnect(save_path);
|
||||||
}
|
}
|
||||||
@@ -1068,7 +1107,7 @@ function save(save_to) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to set the app icon.
|
||||||
*/
|
*/
|
||||||
function changeIcon() {
|
function changeIcon() {
|
||||||
let value = prompt('Enter a new app icon emoji:');
|
let value = prompt('Enter a new app icon emoji:');
|
||||||
@@ -1079,7 +1118,7 @@ function changeIcon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to delete the current app.
|
||||||
*/
|
*/
|
||||||
function deleteApp() {
|
function deleteApp() {
|
||||||
let name = document.getElementById('name');
|
let name = document.getElementById('name');
|
||||||
@@ -1100,8 +1139,8 @@ function deleteApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Get the current app URL.
|
||||||
* @returns
|
* @return The app URL.
|
||||||
*/
|
*/
|
||||||
function url() {
|
function url() {
|
||||||
let hash = window.location.href.indexOf('#');
|
let hash = window.location.href.indexOf('#');
|
||||||
@@ -1119,16 +1158,16 @@ function url() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Get the window hash without the lone '#' if it is empty.
|
||||||
* @returns
|
* @return The hash.
|
||||||
*/
|
*/
|
||||||
function hash() {
|
function hash() {
|
||||||
return window.location.hash != '#' ? window.location.hash : '';
|
return window.location.hash != '#' ? window.location.hash : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Set the iframe document contents.
|
||||||
* @param {*} content
|
* @param content The contents.
|
||||||
*/
|
*/
|
||||||
function api_setDocument(content) {
|
function api_setDocument(content) {
|
||||||
let iframe = document.getElementById('document');
|
let iframe = document.getElementById('document');
|
||||||
@@ -1136,8 +1175,8 @@ function api_setDocument(content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Send a message to the sandboxed iframe.
|
||||||
* @param {*} message
|
* @param message The message.
|
||||||
*/
|
*/
|
||||||
function api_postMessage(message) {
|
function api_postMessage(message) {
|
||||||
let iframe = document.getElementById('document');
|
let iframe = document.getElementById('document');
|
||||||
@@ -1145,8 +1184,8 @@ function api_postMessage(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Show an error.
|
||||||
* @param {*} error
|
* @param error The error.
|
||||||
*/
|
*/
|
||||||
function api_error(error) {
|
function api_error(error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -1160,28 +1199,28 @@ function api_error(error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
et a value in local storage.
|
||||||
* @param {*} key
|
* @param key The key.
|
||||||
* @param {*} value
|
* @param value The value.
|
||||||
*/
|
*/
|
||||||
function api_localStorageSet(key, value) {
|
function api_localStorageSet(key, value) {
|
||||||
window.localStorage.setItem('app:' + key, value);
|
window.localStorage.setItem('app:' + key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Get a value from local storage.
|
||||||
* @param {*} key
|
* @param key The key.
|
||||||
* @returns
|
* @return The value.
|
||||||
*/
|
*/
|
||||||
function api_localStorageGet(key) {
|
function api_localStorageGet(key) {
|
||||||
return window.localStorage.getItem('app:' + key);
|
return window.localStorage.getItem('app:' + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Request a permission
|
||||||
* @param {*} permission
|
* @param permission The permission to request.
|
||||||
* @param {*} id
|
* @param id The id requeesting the permission.
|
||||||
* @returns
|
* @return A promise fulfilled if the permission was granted.
|
||||||
*/
|
*/
|
||||||
function api_requestPermission(permission, id) {
|
function api_requestPermission(permission, id) {
|
||||||
let outer = document.createElement('div');
|
let outer = document.createElement('div');
|
||||||
@@ -1250,23 +1289,23 @@ function api_requestPermission(permission, id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Log from the app to the console.
|
||||||
*/
|
*/
|
||||||
function api_print() {
|
function api_print() {
|
||||||
console.log('app>', ...arguments);
|
console.log('app>', ...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Set the window's location hash.
|
||||||
* @param {*} hash
|
* @param hash The new hash.
|
||||||
*/
|
*/
|
||||||
function api_setHash(hash) {
|
function api_setHash(hash) {
|
||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Process an incoming WebSocket message.
|
||||||
* @param {*} message
|
* @param message The message.
|
||||||
*/
|
*/
|
||||||
function _receive_websocket_message(message) {
|
function _receive_websocket_message(message) {
|
||||||
if (message && message.action == 'session') {
|
if (message && message.action == 'session') {
|
||||||
@@ -1362,9 +1401,9 @@ function _receive_websocket_message(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Set the status message.
|
||||||
* @param {*} message
|
* @param message The message.
|
||||||
* @param {*} color
|
* @param color The message's color.
|
||||||
*/
|
*/
|
||||||
function setStatusMessage(message, color) {
|
function setStatusMessage(message, color) {
|
||||||
document.getElementsByTagName('tf-navigation')[0].status = {
|
document.getElementsByTagName('tf-navigation')[0].status = {
|
||||||
@@ -1375,8 +1414,8 @@ function setStatusMessage(message, color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Send a message to the app.
|
||||||
* @param {*} value
|
* @param value The message.
|
||||||
*/
|
*/
|
||||||
function send(value) {
|
function send(value) {
|
||||||
try {
|
try {
|
||||||
@@ -1389,57 +1428,14 @@ function send(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Notify the app of the window hash changing.
|
||||||
* @param {*} sourceData
|
|
||||||
* @param {*} maxWidth
|
|
||||||
* @param {*} maxHeight
|
|
||||||
* @param {*} callback
|
|
||||||
*/
|
|
||||||
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
|
||||||
let result = sourceData;
|
|
||||||
let image = new Image();
|
|
||||||
image.crossOrigin = 'anonymous';
|
|
||||||
image.referrerPolicy = 'no-referrer';
|
|
||||||
image.onload = function () {
|
|
||||||
if (image.width > maxWidth || image.height > maxHeight) {
|
|
||||||
let downScale = Math.min(
|
|
||||||
maxWidth / image.width,
|
|
||||||
maxHeight / image.height
|
|
||||||
);
|
|
||||||
let canvas = document.createElement('canvas');
|
|
||||||
canvas.width = image.width * downScale;
|
|
||||||
canvas.height = image.height * downScale;
|
|
||||||
let context = canvas.getContext('2d');
|
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
image.width = canvas.width;
|
|
||||||
image.height = canvas.height;
|
|
||||||
context.drawImage(image, 0, 0, image.width, image.height);
|
|
||||||
result = canvas.toDataURL();
|
|
||||||
}
|
|
||||||
callback(result);
|
|
||||||
};
|
|
||||||
image.src = sourceData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
* @param {*} image
|
|
||||||
*/
|
|
||||||
function sendImage(image) {
|
|
||||||
fixImage(image, 320, 240, function (result) {
|
|
||||||
send({image: result});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
*/
|
*/
|
||||||
function hashChange() {
|
function hashChange() {
|
||||||
send({event: 'hashChange', hash: window.location.hash});
|
send({event: 'hashChange', hash: window.location.hash});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Make sure the app is connected on window focus, and notify the app.
|
||||||
*/
|
*/
|
||||||
function focus() {
|
function focus() {
|
||||||
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
||||||
@@ -1450,7 +1446,7 @@ function focus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Notify the app of lost focus.
|
||||||
*/
|
*/
|
||||||
function blur() {
|
function blur() {
|
||||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||||
@@ -1459,8 +1455,8 @@ function blur() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Handle a message.
|
||||||
* @param {*} event
|
* @param event The message.
|
||||||
*/
|
*/
|
||||||
function message(event) {
|
function message(event) {
|
||||||
if (
|
if (
|
||||||
@@ -1508,8 +1504,8 @@ function message(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Reconnect the WebSocket.
|
||||||
* @param {*} path
|
* @param path The path to which the WebSocket should be connected.
|
||||||
*/
|
*/
|
||||||
function reconnect(path) {
|
function reconnect(path) {
|
||||||
let oldSocket = gSocket;
|
let oldSocket = gSocket;
|
||||||
@@ -1524,8 +1520,8 @@ function reconnect(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Connect the WebSocket.
|
||||||
* @param {*} path
|
* @param path The path to which to connect.
|
||||||
*/
|
*/
|
||||||
function connectSocket(path) {
|
function connectSocket(path) {
|
||||||
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
||||||
@@ -1591,8 +1587,8 @@ function connectSocket(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Open a file by name.
|
||||||
* @param {*} name
|
* @param name The file to open.
|
||||||
*/
|
*/
|
||||||
function openFile(name) {
|
function openFile(name) {
|
||||||
let newDoc =
|
let newDoc =
|
||||||
@@ -1617,7 +1613,7 @@ function openFile(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Refresh the files list.
|
||||||
*/
|
*/
|
||||||
function updateFiles() {
|
function updateFiles() {
|
||||||
let files = document.getElementsByTagName('tf-files-pane')[0];
|
let files = document.getElementsByTagName('tf-files-pane')[0];
|
||||||
@@ -1639,8 +1635,8 @@ function updateFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Create a new file with the given name.
|
||||||
* @param {*} name
|
* @param name The file's name.
|
||||||
*/
|
*/
|
||||||
function makeNewFile(name) {
|
function makeNewFile(name) {
|
||||||
gFiles[name] = {
|
gFiles[name] = {
|
||||||
@@ -1650,7 +1646,7 @@ function makeNewFile(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to create a new file.
|
||||||
*/
|
*/
|
||||||
function newFile() {
|
function newFile() {
|
||||||
let name = prompt('Name of new file:', 'file.js');
|
let name = prompt('Name of new file:', 'file.js');
|
||||||
@@ -1660,7 +1656,7 @@ function newFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to remove a file.
|
||||||
*/
|
*/
|
||||||
function removeFile() {
|
function removeFile() {
|
||||||
if (confirm('Remove ' + gCurrentFile + '?')) {
|
if (confirm('Remove ' + gCurrentFile + '?')) {
|
||||||
@@ -1670,7 +1666,7 @@ function removeFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Export the app to a zip file, which is downloaded by the browser.
|
||||||
*/
|
*/
|
||||||
async function appExport() {
|
async function appExport() {
|
||||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||||
@@ -1701,10 +1697,10 @@ async function appExport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Save a file.
|
||||||
* @param {*} name
|
* @param name The file to svae.
|
||||||
* @param {*} file
|
* @param file The file contents.
|
||||||
* @returns
|
* @return A promise resolved with the blob ID of the saved file.
|
||||||
*/
|
*/
|
||||||
async function save_file_to_blob_id(name, file) {
|
async function save_file_to_blob_id(name, file) {
|
||||||
console.log(`Saving ${name}.`);
|
console.log(`Saving ${name}.`);
|
||||||
@@ -1728,7 +1724,7 @@ async function save_file_to_blob_id(name, file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to import an app from a zip file.
|
||||||
*/
|
*/
|
||||||
async function appImport() {
|
async function appImport() {
|
||||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||||
@@ -1799,7 +1795,7 @@ async function appImport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Prettify the current source file.
|
||||||
*/
|
*/
|
||||||
async function sourcePretty() {
|
async function sourcePretty() {
|
||||||
let prettier = (await import('/prettier/standalone.mjs')).default;
|
let prettier = (await import('/prettier/standalone.mjs')).default;
|
||||||
@@ -1827,6 +1823,9 @@ async function sourcePretty() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle visible whitespace.
|
||||||
|
*/
|
||||||
function toggleVisibleWhitespace() {
|
function toggleVisibleWhitespace() {
|
||||||
let editor_style = document.getElementById('editor_style');
|
let editor_style = document.getElementById('editor_style');
|
||||||
/*
|
/*
|
||||||
@@ -1852,7 +1851,9 @@ function toggleVisibleWhitespace() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODOC
|
/**
|
||||||
|
* Register event handlers and connect the WebSocket on load.
|
||||||
|
*/
|
||||||
window.addEventListener('load', function () {
|
window.addEventListener('load', function () {
|
||||||
window.addEventListener('hashchange', hashChange);
|
window.addEventListener('hashchange', hashChange);
|
||||||
window.addEventListener('focus', focus);
|
window.addEventListener('focus', focus);
|
||||||
@@ -1900,3 +1901,5 @@ window.addEventListener('load', function () {
|
|||||||
toggleVisibleWhitespace();
|
toggleVisibleWhitespace();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
135
core/core.js
135
core/core.js
@@ -1,12 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \defgroup tfcore Tilde Friends Core JS
|
||||||
|
* Tilde Friends process management, in JavaScript.
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \cond */
|
||||||
import * as app from './app.js';
|
import * as app from './app.js';
|
||||||
import * as http from './http.js';
|
import * as http from './http.js';
|
||||||
|
|
||||||
let gProcesses = {};
|
export {invoke, getProcessBlob};
|
||||||
let gStatsTimer = false;
|
/** \endcond */
|
||||||
let g_handler_index = 0;
|
|
||||||
|
|
||||||
|
/** All running processes. */
|
||||||
|
let gProcesses = {};
|
||||||
|
/** Whether stats are currently being sent. */
|
||||||
|
let gStatsTimer = false;
|
||||||
|
/** Effectively a process ID. */
|
||||||
|
let g_handler_index = 0;
|
||||||
|
/** Time between pings, in milliseconds. */
|
||||||
const k_ping_interval = 60 * 1000;
|
const k_ping_interval = 60 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print an error.
|
||||||
|
* @param error The error.
|
||||||
|
*/
|
||||||
function printError(error) {
|
function printError(error) {
|
||||||
if (error.stackTrace) {
|
if (error.stackTrace) {
|
||||||
print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||||
@@ -19,6 +37,12 @@ function printError(error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a handler.
|
||||||
|
* @param handlers The handlers on which to invoke the callback.
|
||||||
|
* @param argv Arguments to pass to the handlers.
|
||||||
|
* @return A promise.
|
||||||
|
*/
|
||||||
function invoke(handlers, argv) {
|
function invoke(handlers, argv) {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
if (handlers) {
|
if (handlers) {
|
||||||
@@ -39,6 +63,12 @@ function invoke(handlers, argv) {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a named event to all registered apps.
|
||||||
|
* @param eventName the name of the event.
|
||||||
|
* @param argv Arguments to pass to the handlers.
|
||||||
|
* @return A promise.
|
||||||
|
*/
|
||||||
function broadcastEvent(eventName, argv) {
|
function broadcastEvent(eventName, argv) {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let process of Object.values(gProcesses)) {
|
for (let process of Object.values(gProcesses)) {
|
||||||
@@ -49,6 +79,11 @@ function broadcastEvent(eventName, argv) {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to all other instances of the same app.
|
||||||
|
* @param message The message.
|
||||||
|
* @return A promise.
|
||||||
|
*/
|
||||||
function broadcast(message) {
|
function broadcast(message) {
|
||||||
let sender = this;
|
let sender = this;
|
||||||
let promises = [];
|
let promises = [];
|
||||||
@@ -65,6 +100,15 @@ function broadcast(message) {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to all instances of the same app running as the same user.
|
||||||
|
* @param user The user.
|
||||||
|
* @param packageOwner The owner of the app.
|
||||||
|
* @param packageName The name of the app.
|
||||||
|
* @param eventName The name of the event.
|
||||||
|
* @param argv The arguments to pass.
|
||||||
|
* @return A promise.
|
||||||
|
*/
|
||||||
function broadcastAppEventToUser(
|
function broadcastAppEventToUser(
|
||||||
user,
|
user,
|
||||||
packageOwner,
|
packageOwner,
|
||||||
@@ -87,6 +131,11 @@ function broadcastAppEventToUser(
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user context information for a call.
|
||||||
|
* @param caller The calling process.
|
||||||
|
* @param process The receiving process.
|
||||||
|
*/
|
||||||
function getUser(caller, process) {
|
function getUser(caller, process) {
|
||||||
return {
|
return {
|
||||||
key: process.key,
|
key: process.key,
|
||||||
@@ -97,38 +146,26 @@ function getUser(caller, process) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getApps(user, process) {
|
/**
|
||||||
if (
|
* Send a message.
|
||||||
process.credentials &&
|
* @param from The calling process.
|
||||||
process.credentials.session &&
|
* @param to The receiving process.
|
||||||
process.credentials.session.name
|
* @param message The message.
|
||||||
) {
|
* @return A promise.
|
||||||
if (user && user !== process.credentials.session.name && user !== 'core') {
|
*/
|
||||||
return {};
|
|
||||||
} else if (!user) {
|
|
||||||
user = process.credentials.session.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (user) {
|
|
||||||
let db = new Database(user);
|
|
||||||
try {
|
|
||||||
let names = JSON.parse(await db.get('apps'));
|
|
||||||
let result = {};
|
|
||||||
for (let name of names) {
|
|
||||||
result[name] = await db.get('path:' + name);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function postMessageInternal(from, to, message) {
|
function postMessageInternal(from, to, message) {
|
||||||
if (to.eventHandlers['message']) {
|
if (to.eventHandlers['message']) {
|
||||||
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create a process for an app blob.
|
||||||
|
* @param blobId The blob identifier.
|
||||||
|
* @param key A unique key for the invocation.
|
||||||
|
* @param options Other options.
|
||||||
|
* @return The process.
|
||||||
|
*/
|
||||||
async function getProcessBlob(blobId, key, options) {
|
async function getProcessBlob(blobId, key, options) {
|
||||||
let process = gProcesses[key];
|
let process = gProcesses[key];
|
||||||
if (!process && !(options && 'create' in options && !options.create)) {
|
if (!process && !(options && 'create' in options && !options.create)) {
|
||||||
@@ -162,23 +199,6 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
let imports = {
|
let imports = {
|
||||||
core: {
|
core: {
|
||||||
broadcast: broadcast.bind(process),
|
broadcast: broadcast.bind(process),
|
||||||
register: function (eventName, handler) {
|
|
||||||
if (!process.eventHandlers[eventName]) {
|
|
||||||
process.eventHandlers[eventName] = [];
|
|
||||||
}
|
|
||||||
process.eventHandlers[eventName].push(handler);
|
|
||||||
},
|
|
||||||
unregister: function (eventName, handler) {
|
|
||||||
if (process.eventHandlers[eventName]) {
|
|
||||||
let index = process.eventHandlers[eventName].indexOf(handler);
|
|
||||||
if (index != -1) {
|
|
||||||
process.eventHandlers[eventName].splice(index, 1);
|
|
||||||
}
|
|
||||||
if (process.eventHandlers[eventName].length == 0) {
|
|
||||||
delete process.eventHandlers[eventName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
user: getUser(process, process),
|
user: getUser(process, process),
|
||||||
users: async function () {
|
users: async function () {
|
||||||
try {
|
try {
|
||||||
@@ -220,7 +240,6 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
let settings = await loadSettings();
|
let settings = await loadSettings();
|
||||||
return settings?.permissions?.[user] ?? [];
|
return settings?.permissions?.[user] ?? [];
|
||||||
},
|
},
|
||||||
apps: (user) => getApps(user, process),
|
|
||||||
getSockets: getSockets,
|
getSockets: getSockets,
|
||||||
permissionTest: async function (permission) {
|
permissionTest: async function (permission) {
|
||||||
let user = process?.credentials?.session?.name;
|
let user = process?.credentials?.session?.name;
|
||||||
@@ -667,6 +686,9 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSB message added callback.
|
||||||
|
*/
|
||||||
ssb.addEventListener('message', function () {
|
ssb.addEventListener('message', function () {
|
||||||
broadcastEvent('onMessage', [...arguments]);
|
broadcastEvent('onMessage', [...arguments]);
|
||||||
});
|
});
|
||||||
@@ -679,6 +701,10 @@ ssb.addEventListener('connections', function () {
|
|||||||
broadcastEvent('onConnectionsChanged', []);
|
broadcastEvent('onConnectionsChanged', []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load settings from the database.
|
||||||
|
* @return The settings as a key value pairs object.
|
||||||
|
*/
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
let data = {};
|
let data = {};
|
||||||
try {
|
try {
|
||||||
@@ -697,6 +723,9 @@ async function loadSettings() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send periodic stats to all clients.
|
||||||
|
*/
|
||||||
function sendStats() {
|
function sendStats() {
|
||||||
let apps = Object.values(gProcesses)
|
let apps = Object.values(gProcesses)
|
||||||
.filter((process) => process.app)
|
.filter((process) => process.app)
|
||||||
@@ -712,6 +741,16 @@ function sendStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke an app's handler.js.
|
||||||
|
* @param response The response object.
|
||||||
|
* @param app_blob_id The app's blob identifier.
|
||||||
|
* @param path The request path.
|
||||||
|
* @param query The request query string.
|
||||||
|
* @param headers The request headers.
|
||||||
|
* @param package_owner The app's owner.
|
||||||
|
* @param package_name The app's name.
|
||||||
|
*/
|
||||||
exports.callAppHandler = async function callAppHandler(
|
exports.callAppHandler = async function callAppHandler(
|
||||||
response,
|
response,
|
||||||
app_blob_id,
|
app_blob_id,
|
||||||
@@ -777,4 +816,4 @@ exports.callAppHandler = async function callAppHandler(
|
|||||||
response.end(answer?.data);
|
response.end(answer?.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {invoke, getProcessBlob};
|
/** @} */
|
||||||
|
34
core/http.js
34
core/http.js
@@ -1,8 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* TODOC
|
* \file
|
||||||
* TODO: document so we can improve this
|
* \defgroup tfhttp Tilde Friends HTTP Client JS
|
||||||
* @param {*} url
|
* Tilde Friends server-side HTTP client.
|
||||||
* @returns
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a URL into protocol, host, path, and port parts.
|
||||||
|
* @param url
|
||||||
|
* @return An object of the URL parts.
|
||||||
*/
|
*/
|
||||||
function parseUrl(url) {
|
function parseUrl(url) {
|
||||||
// XXX: Hack.
|
// XXX: Hack.
|
||||||
@@ -16,9 +22,9 @@ function parseUrl(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Parse an HTTP response into headers and body content.
|
||||||
* @param {*} data
|
* @param data The response data, headers and body included.
|
||||||
* @returns
|
* @return headers and body data.
|
||||||
*/
|
*/
|
||||||
function parseResponse(data) {
|
function parseResponse(data) {
|
||||||
let firstLine;
|
let firstLine;
|
||||||
@@ -36,15 +42,15 @@ function parseResponse(data) {
|
|||||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {body: data};
|
return {headers: headers, body: data};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Make an HTTP request.
|
||||||
* @param {*} url
|
* @param url The URL.
|
||||||
* @param {*} options
|
* @param options Request options.
|
||||||
* @param {*} allowed_hosts
|
* @param allowed_hosts List of allowed hosts.
|
||||||
* @returns
|
* @return A promise resolved with the response headers and body.
|
||||||
*/
|
*/
|
||||||
export function fetch(url, options, allowed_hosts) {
|
export function fetch(url, options, allowed_hosts) {
|
||||||
let parsed = parseUrl(url);
|
let parsed = parseUrl(url);
|
||||||
@@ -111,3 +117,5 @@ export function fetch(url, options, allowed_hosts) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
@@ -1,11 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \defgroup tfrpc Tilde Friends RPC.
|
||||||
|
* Tilde Friends RPC.
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Whether this module is being run in a web browser. */
|
||||||
const k_is_browser = get_is_browser();
|
const k_is_browser = get_is_browser();
|
||||||
|
/** Registered methods. */
|
||||||
let g_api = {};
|
let g_api = {};
|
||||||
|
/** The next method identifier. */
|
||||||
let g_next_id = 1;
|
let g_next_id = 1;
|
||||||
|
/** Identifiers of pending calls. */
|
||||||
let g_calls = {};
|
let g_calls = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Check if being called from a browser vs. server-side.
|
||||||
* @returns
|
* @return true if called from a browser.
|
||||||
*/
|
*/
|
||||||
function get_is_browser() {
|
function get_is_browser() {
|
||||||
try {
|
try {
|
||||||
@@ -15,16 +26,30 @@ function get_is_browser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** \cond */
|
||||||
if (k_is_browser) {
|
if (k_is_browser) {
|
||||||
print = console.log;
|
print = console.log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (k_is_browser) {
|
||||||
|
window.addEventListener('message', function (event) {
|
||||||
|
call_rpc(event.data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
core.register('message', function (message) {
|
||||||
|
call_rpc(message?.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export let rpc = new Proxy({}, {get: make_rpc});
|
||||||
|
/** \endcond */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Make a function to invoke a remote procedure.
|
||||||
* @param {*} target
|
* @param target The target.
|
||||||
* @param {*} prop
|
* @param prop The name of the function.
|
||||||
* @param {*} receiver
|
* @param receiver The receiver.
|
||||||
* @returns
|
* @return A function.
|
||||||
*/
|
*/
|
||||||
function make_rpc(target, prop, receiver) {
|
function make_rpc(target, prop, receiver) {
|
||||||
return function () {
|
return function () {
|
||||||
@@ -55,8 +80,8 @@ function make_rpc(target, prop, receiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Send a response.
|
||||||
* @param {*} response
|
* @param response The response.
|
||||||
*/
|
*/
|
||||||
function send(response) {
|
function send(response) {
|
||||||
if (k_is_browser) {
|
if (k_is_browser) {
|
||||||
@@ -67,8 +92,8 @@ function send(response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Invoke a remote procedure.
|
||||||
* @param {*} message
|
* @param message An object describing the call.
|
||||||
*/
|
*/
|
||||||
function call_rpc(message) {
|
function call_rpc(message) {
|
||||||
if (message && message.message === 'tfrpc') {
|
if (message && message.message === 'tfrpc') {
|
||||||
@@ -112,22 +137,12 @@ function call_rpc(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (k_is_browser) {
|
|
||||||
window.addEventListener('message', function (event) {
|
|
||||||
call_rpc(event.data);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
core.register('message', function (message) {
|
|
||||||
call_rpc(message?.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export let rpc = new Proxy({}, {get: make_rpc});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Register a function that to be called remotely.
|
||||||
* @param {*} method
|
* @param method The method.
|
||||||
*/
|
*/
|
||||||
export function register(method) {
|
export function register(method) {
|
||||||
g_api[method.name] = method;
|
g_api[method.name] = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
@@ -25,14 +25,14 @@
|
|||||||
}:
|
}:
|
||||||
pkgs.stdenv.mkDerivation rec {
|
pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "tildefriends";
|
pname = "tildefriends";
|
||||||
version = "0.0.32";
|
version = "0.2025.8";
|
||||||
|
|
||||||
src = pkgs.fetchFromGitea {
|
src = pkgs.fetchFromGitea {
|
||||||
domain = "dev.tildefriends.net";
|
domain = "dev.tildefriends.net";
|
||||||
owner = "cory";
|
owner = "cory";
|
||||||
repo = "tildefriends";
|
repo = "tildefriends";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = "sha256-Dk0NOEQIg2LeENySK0+MgpZEtfsClGq6dZL+eOOpE0U=";
|
hash = "sha256-N/5lp8RL19B6Z43kRxx7c01WVJkK44a/wwNgRJPk5uI=";
|
||||||
fetchSubmodules = true;
|
fetchSubmodules = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
379
deps/codemirror_src/package-lock.json
generated
vendored
379
deps/codemirror_src/package-lock.json
generated
vendored
@@ -19,9 +19,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/autocomplete": {
|
"node_modules/@codemirror/autocomplete": {
|
||||||
"version": "6.18.6",
|
"version": "6.18.7",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz",
|
||||||
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
"integrity": "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"version": "6.8.1",
|
"version": "6.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||||
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
"@codemirror/state": "^6.4.0",
|
"@codemirror/state": "^6.4.0",
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.0.0",
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
@@ -56,6 +59,7 @@
|
|||||||
"version": "6.4.9",
|
"version": "6.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||||
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.0.0",
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
"@codemirror/lang-css": "^6.0.0",
|
"@codemirror/lang-css": "^6.0.0",
|
||||||
@@ -72,6 +76,7 @@
|
|||||||
"version": "6.2.4",
|
"version": "6.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||||
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.0.0",
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
"@codemirror/language": "^6.6.0",
|
"@codemirror/language": "^6.6.0",
|
||||||
@@ -86,15 +91,17 @@
|
|||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
||||||
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
"@lezer/json": "^1.0.0"
|
"@lezer/json": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/language": {
|
"node_modules/@codemirror/language": {
|
||||||
"version": "6.11.2",
|
"version": "6.11.3",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
"integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==",
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.23.0",
|
"@codemirror/view": "^6.23.0",
|
||||||
@@ -108,6 +115,7 @@
|
|||||||
"version": "6.8.5",
|
"version": "6.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||||
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.35.0",
|
"@codemirror/view": "^6.35.0",
|
||||||
@@ -118,6 +126,7 @@
|
|||||||
"version": "6.5.11",
|
"version": "6.5.11",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||||
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.0.0",
|
"@codemirror/view": "^6.0.0",
|
||||||
@@ -128,6 +137,7 @@
|
|||||||
"version": "6.5.2",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@marijn/find-cluster-break": "^1.0.0"
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -136,6 +146,7 @@
|
|||||||
"version": "6.1.3",
|
"version": "6.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||||
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
@@ -144,9 +155,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.38.0",
|
"version": "6.38.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.0.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.2.tgz",
|
||||||
"integrity": "sha512-yvSchUwHOdupXkd7xJ0ob36jdsSR/I+/C+VbY0ffBiL5NiSTEBDfB1ZGWbbIlDd5xgdUkody+lukAdOxYrOBeg==",
|
"integrity": "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.5.0",
|
"@codemirror/state": "^6.5.0",
|
||||||
"crelt": "^1.0.6",
|
"crelt": "^1.0.6",
|
||||||
@@ -155,17 +167,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.8",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.2.1",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
@@ -173,40 +182,35 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"license": "MIT",
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/set-array": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/source-map": {
|
"node_modules/@jridgewell/source-map": {
|
||||||
"version": "0.3.6",
|
"version": "0.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
"@jridgewell/trace-mapping": "^0.3.25"
|
"@jridgewell/trace-mapping": "^0.3.25"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.25",
|
"version": "0.3.30",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
|
||||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
@@ -215,12 +219,14 @@
|
|||||||
"node_modules/@lezer/common": {
|
"node_modules/@lezer/common": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/css": {
|
"node_modules/@lezer/css": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||||
"integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
|
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.2.0",
|
"@lezer/common": "^1.2.0",
|
||||||
"@lezer/highlight": "^1.0.0",
|
"@lezer/highlight": "^1.0.0",
|
||||||
@@ -231,6 +237,7 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.0.0"
|
"@lezer/common": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -239,6 +246,7 @@
|
|||||||
"version": "1.3.10",
|
"version": "1.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
||||||
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.2.0",
|
"@lezer/common": "^1.2.0",
|
||||||
"@lezer/highlight": "^1.0.0",
|
"@lezer/highlight": "^1.0.0",
|
||||||
@@ -246,9 +254,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/javascript": {
|
"node_modules/@lezer/javascript": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.3.tgz",
|
||||||
"integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
|
"integrity": "sha512-jexmlKq5NpGiB7t+0QkyhSXRgaiab5YisHIQW9C7EcU19KSUsDguZe9WY+rmRDg34nXoNH2LQ4SxpC+aJUchSQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.2.0",
|
"@lezer/common": "^1.2.0",
|
||||||
"@lezer/highlight": "^1.1.3",
|
"@lezer/highlight": "^1.1.3",
|
||||||
@@ -259,6 +268,7 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.2.0",
|
"@lezer/common": "^1.2.0",
|
||||||
"@lezer/highlight": "^1.0.0",
|
"@lezer/highlight": "^1.0.0",
|
||||||
@@ -269,6 +279,7 @@
|
|||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.0.0"
|
"@lezer/common": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -276,12 +287,14 @@
|
|||||||
"node_modules/@marijn/find-cluster-break": {
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/plugin-node-resolve": {
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
"version": "15.3.1",
|
"version": "15.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
||||||
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
|
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/pluginutils": "^5.0.1",
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
"@types/resolve": "1.20.2",
|
"@types/resolve": "1.20.2",
|
||||||
@@ -306,6 +319,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
||||||
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
|
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"serialize-javascript": "^6.0.1",
|
"serialize-javascript": "^6.0.1",
|
||||||
"smob": "^1.0.0",
|
"smob": "^1.0.0",
|
||||||
@@ -324,9 +338,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/pluginutils": {
|
"node_modules/@rollup/pluginutils": {
|
||||||
"version": "5.2.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
|
||||||
"integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==",
|
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "^1.0.0",
|
"@types/estree": "^1.0.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
@@ -345,240 +360,273 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz",
|
||||||
"integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==",
|
"integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz",
|
||||||
"integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==",
|
"integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz",
|
||||||
"integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==",
|
"integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz",
|
||||||
"integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==",
|
"integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz",
|
||||||
"integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==",
|
"integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz",
|
||||||
"integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==",
|
"integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz",
|
||||||
"integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==",
|
"integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz",
|
||||||
"integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==",
|
"integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz",
|
||||||
"integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==",
|
"integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz",
|
||||||
"integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==",
|
"integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz",
|
||||||
"integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==",
|
"integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz",
|
||||||
"integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==",
|
"integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz",
|
||||||
"integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==",
|
"integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz",
|
||||||
"integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==",
|
"integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz",
|
||||||
"integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==",
|
"integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz",
|
||||||
"integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==",
|
"integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz",
|
||||||
"integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==",
|
"integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz",
|
||||||
"integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==",
|
"integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
|
"version": "4.50.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz",
|
||||||
|
"integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz",
|
||||||
"integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==",
|
"integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz",
|
||||||
"integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==",
|
"integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@@ -587,18 +635,21 @@
|
|||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
|
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.15.0",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -610,12 +661,14 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/codemirror": {
|
"node_modules/codemirror": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.0.0",
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
"@codemirror/commands": "^6.0.0",
|
"@codemirror/commands": "^6.0.0",
|
||||||
@@ -630,17 +683,20 @@
|
|||||||
"version": "2.20.3",
|
"version": "2.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/crelt": {
|
"node_modules/crelt": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/deepmerge": {
|
"node_modules/deepmerge": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -648,13 +704,15 @@
|
|||||||
"node_modules/estree-walker": {
|
"node_modules/estree-walker": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@@ -667,6 +725,7 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -675,6 +734,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
@@ -686,6 +746,7 @@
|
|||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
},
|
},
|
||||||
@@ -699,17 +760,20 @@
|
|||||||
"node_modules/is-module": {
|
"node_modules/is-module": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
|
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -722,6 +786,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
@@ -730,6 +795,7 @@
|
|||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.16.0",
|
"is-core-module": "^2.16.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
@@ -746,9 +812,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.44.1",
|
"version": "4.50.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz",
|
||||||
"integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==",
|
"integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
@@ -760,26 +827,27 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.44.1",
|
"@rollup/rollup-android-arm-eabi": "4.50.1",
|
||||||
"@rollup/rollup-android-arm64": "4.44.1",
|
"@rollup/rollup-android-arm64": "4.50.1",
|
||||||
"@rollup/rollup-darwin-arm64": "4.44.1",
|
"@rollup/rollup-darwin-arm64": "4.50.1",
|
||||||
"@rollup/rollup-darwin-x64": "4.44.1",
|
"@rollup/rollup-darwin-x64": "4.50.1",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.44.1",
|
"@rollup/rollup-freebsd-arm64": "4.50.1",
|
||||||
"@rollup/rollup-freebsd-x64": "4.44.1",
|
"@rollup/rollup-freebsd-x64": "4.50.1",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.44.1",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.50.1",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.44.1",
|
"@rollup/rollup-linux-arm-musleabihf": "4.50.1",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.44.1",
|
"@rollup/rollup-linux-arm64-gnu": "4.50.1",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.44.1",
|
"@rollup/rollup-linux-arm64-musl": "4.50.1",
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": "4.44.1",
|
"@rollup/rollup-linux-loongarch64-gnu": "4.50.1",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.44.1",
|
"@rollup/rollup-linux-ppc64-gnu": "4.50.1",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.44.1",
|
"@rollup/rollup-linux-riscv64-gnu": "4.50.1",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.44.1",
|
"@rollup/rollup-linux-riscv64-musl": "4.50.1",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.44.1",
|
"@rollup/rollup-linux-s390x-gnu": "4.50.1",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.44.1",
|
"@rollup/rollup-linux-x64-gnu": "4.50.1",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.44.1",
|
"@rollup/rollup-linux-x64-musl": "4.50.1",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.44.1",
|
"@rollup/rollup-openharmony-arm64": "4.50.1",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.44.1",
|
"@rollup/rollup-win32-arm64-msvc": "4.50.1",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.44.1",
|
"@rollup/rollup-win32-ia32-msvc": "4.50.1",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.50.1",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -801,13 +869,15 @@
|
|||||||
"type": "consulting",
|
"type": "consulting",
|
||||||
"url": "https://feross.org/support"
|
"url": "https://feross.org/support"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/serialize-javascript": {
|
"node_modules/serialize-javascript": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
|
||||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"randombytes": "^2.1.0"
|
"randombytes": "^2.1.0"
|
||||||
}
|
}
|
||||||
@@ -816,13 +886,15 @@
|
|||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
|
||||||
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
|
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -832,6 +904,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
@@ -840,12 +913,14 @@
|
|||||||
"node_modules/style-mod": {
|
"node_modules/style-mod": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/supports-preserve-symlinks-flag": {
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -854,13 +929,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.43.1",
|
"version": "5.44.0",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||||
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
|
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.14.0",
|
"acorn": "^8.15.0",
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
"source-map-support": "~0.5.20"
|
"source-map-support": "~0.5.20"
|
||||||
},
|
},
|
||||||
@@ -874,7 +950,8 @@
|
|||||||
"node_modules/w3c-keyname": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
deps/lit/lit-all.min.js
vendored
8
deps/lit/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/lit/lit-all.min.js.map
vendored
2
deps/lit/lit-all.min.js.map
vendored
File diff suppressed because one or more lines are too long
2
deps/openssl_src
vendored
2
deps/openssl_src
vendored
Submodule deps/openssl_src updated: aea7aaf2ab...0893a62353
2
deps/picohttpparser
vendored
2
deps/picohttpparser
vendored
Submodule deps/picohttpparser updated: f8d0513f1a...f8326098f6
2
deps/speedscope/index.html
vendored
2
deps/speedscope/index.html
vendored
@@ -11,7 +11,7 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="speedscope-VHEG2FVF.js"></script>
|
<script src="speedscope-HCR63FMT.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
6
deps/speedscope/release.txt
vendored
6
deps/speedscope/release.txt
vendored
@@ -1,3 +1,3 @@
|
|||||||
speedscope@1.22.2
|
speedscope@1.23.1
|
||||||
Sat Feb 15 13:02:38 PST 2025
|
Mon Aug 11 11:43:09 PDT 2025
|
||||||
1c254dcb3e2b4f6d921340d20e972d9d27b788f4
|
0cec0f82c334aed6cf19d43cabeadcda0d95e0fc
|
||||||
|
File diff suppressed because one or more lines are too long
203
deps/sqlite/sqlite3.c
vendored
203
deps/sqlite/sqlite3.c
vendored
@@ -1,6 +1,6 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
** This file is an amalgamation of many separate C source files from SQLite
|
** This file is an amalgamation of many separate C source files from SQLite
|
||||||
** version 3.50.2. By combining all the individual C code files into this
|
** version 3.50.4. By combining all the individual C code files into this
|
||||||
** single large file, the entire code can be compiled as a single translation
|
** single large file, the entire code can be compiled as a single translation
|
||||||
** unit. This allows many compilers to do optimizations that would not be
|
** unit. This allows many compilers to do optimizations that would not be
|
||||||
** possible if the files were compiled separately. Performance improvements
|
** possible if the files were compiled separately. Performance improvements
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
** separate file. This file contains only code for the core SQLite library.
|
** separate file. This file contains only code for the core SQLite library.
|
||||||
**
|
**
|
||||||
** The content in this amalgamation comes from Fossil check-in
|
** The content in this amalgamation comes from Fossil check-in
|
||||||
** 2af157d77fb1304a74176eaee7fbc7c7e932 with changes in files:
|
** 4d8adfb30e03f9cf27f800a2c1ba3c48fb4c with changes in files:
|
||||||
**
|
**
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
@@ -465,9 +465,9 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.50.2"
|
#define SQLITE_VERSION "3.50.4"
|
||||||
#define SQLITE_VERSION_NUMBER 3050002
|
#define SQLITE_VERSION_NUMBER 3050004
|
||||||
#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079"
|
#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@@ -9377,13 +9377,13 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
||||||
** <dd>^This is the number of sort operations that have occurred.
|
** <dd>^This is the number of sort operations that have occurred.
|
||||||
** A non-zero value in this counter may indicate an opportunity to
|
** A non-zero value in this counter may indicate an opportunity to
|
||||||
** improvement performance through careful use of indices.</dd>
|
** improve performance through careful use of indices.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
|
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
|
||||||
** <dd>^This is the number of rows inserted into transient indices that
|
** <dd>^This is the number of rows inserted into transient indices that
|
||||||
** were created automatically in order to help joins run faster.
|
** were created automatically in order to help joins run faster.
|
||||||
** A non-zero value in this counter may indicate an opportunity to
|
** A non-zero value in this counter may indicate an opportunity to
|
||||||
** improvement performance by adding permanent indices that do not
|
** improve performance by adding permanent indices that do not
|
||||||
** need to be reinitialized each time the statement is run.</dd>
|
** need to be reinitialized each time the statement is run.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
||||||
@@ -9392,19 +9392,19 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** to 2147483647. The number of virtual machine operations can be
|
** to 2147483647. The number of virtual machine operations can be
|
||||||
** used as a proxy for the total work done by the prepared statement.
|
** used as a proxy for the total work done by the prepared statement.
|
||||||
** If the number of virtual machine operations exceeds 2147483647
|
** If the number of virtual machine operations exceeds 2147483647
|
||||||
** then the value returned by this statement status code is undefined.
|
** then the value returned by this statement status code is undefined.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
|
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
|
||||||
** <dd>^This is the number of times that the prepare statement has been
|
** <dd>^This is the number of times that the prepare statement has been
|
||||||
** automatically regenerated due to schema changes or changes to
|
** automatically regenerated due to schema changes or changes to
|
||||||
** [bound parameters] that might affect the query plan.
|
** [bound parameters] that might affect the query plan.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
|
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
|
||||||
** <dd>^This is the number of times that the prepared statement has
|
** <dd>^This is the number of times that the prepared statement has
|
||||||
** been run. A single "run" for the purposes of this counter is one
|
** been run. A single "run" for the purposes of this counter is one
|
||||||
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
|
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
|
||||||
** The counter is incremented on the first [sqlite3_step()] call of each
|
** The counter is incremented on the first [sqlite3_step()] call of each
|
||||||
** cycle.
|
** cycle.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
||||||
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
||||||
@@ -9414,7 +9414,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** step was bypassed because a Bloom filter returned not-found. The
|
** step was bypassed because a Bloom filter returned not-found. The
|
||||||
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
||||||
** times that the Bloom filter returned a find, and thus the join step
|
** times that the Bloom filter returned a find, and thus the join step
|
||||||
** had to be processed as normal.
|
** had to be processed as normal.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
|
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
|
||||||
** <dd>^This is the approximate number of bytes of heap memory
|
** <dd>^This is the approximate number of bytes of heap memory
|
||||||
@@ -9519,9 +9519,9 @@ struct sqlite3_pcache_page {
|
|||||||
** SQLite will typically create one cache instance for each open database file,
|
** SQLite will typically create one cache instance for each open database file,
|
||||||
** though this is not guaranteed. ^The
|
** though this is not guaranteed. ^The
|
||||||
** first parameter, szPage, is the size in bytes of the pages that must
|
** first parameter, szPage, is the size in bytes of the pages that must
|
||||||
** be allocated by the cache. ^szPage will always a power of two. ^The
|
** be allocated by the cache. ^szPage will always be a power of two. ^The
|
||||||
** second parameter szExtra is a number of bytes of extra storage
|
** second parameter szExtra is a number of bytes of extra storage
|
||||||
** associated with each page cache entry. ^The szExtra parameter will
|
** associated with each page cache entry. ^The szExtra parameter will be
|
||||||
** a number less than 250. SQLite will use the
|
** a number less than 250. SQLite will use the
|
||||||
** extra szExtra bytes on each page to store metadata about the underlying
|
** extra szExtra bytes on each page to store metadata about the underlying
|
||||||
** database page on disk. The value passed into szExtra depends
|
** database page on disk. The value passed into szExtra depends
|
||||||
@@ -9529,17 +9529,17 @@ struct sqlite3_pcache_page {
|
|||||||
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
|
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
|
||||||
** created will be used to cache database pages of a file stored on disk, or
|
** created will be used to cache database pages of a file stored on disk, or
|
||||||
** false if it is used for an in-memory database. The cache implementation
|
** false if it is used for an in-memory database. The cache implementation
|
||||||
** does not have to do anything special based with the value of bPurgeable;
|
** does not have to do anything special based upon the value of bPurgeable;
|
||||||
** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will
|
** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will
|
||||||
** never invoke xUnpin() except to deliberately delete a page.
|
** never invoke xUnpin() except to deliberately delete a page.
|
||||||
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
||||||
** false will always have the "discard" flag set to true.
|
** false will always have the "discard" flag set to true.
|
||||||
** ^Hence, a cache created with bPurgeable false will
|
** ^Hence, a cache created with bPurgeable set to false will
|
||||||
** never contain any unpinned pages.
|
** never contain any unpinned pages.
|
||||||
**
|
**
|
||||||
** [[the xCachesize() page cache method]]
|
** [[the xCachesize() page cache method]]
|
||||||
** ^(The xCachesize() method may be called at any time by SQLite to set the
|
** ^(The xCachesize() method may be called at any time by SQLite to set the
|
||||||
** suggested maximum cache-size (number of pages stored by) the cache
|
** suggested maximum cache-size (number of pages stored) for the cache
|
||||||
** instance passed as the first argument. This is the value configured using
|
** instance passed as the first argument. This is the value configured using
|
||||||
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
||||||
** parameter, the implementation is not required to do anything with this
|
** parameter, the implementation is not required to do anything with this
|
||||||
@@ -9566,12 +9566,12 @@ struct sqlite3_pcache_page {
|
|||||||
** implementation must return a pointer to the page buffer with its content
|
** implementation must return a pointer to the page buffer with its content
|
||||||
** intact. If the requested page is not already in the cache, then the
|
** intact. If the requested page is not already in the cache, then the
|
||||||
** cache implementation should use the value of the createFlag
|
** cache implementation should use the value of the createFlag
|
||||||
** parameter to help it determined what action to take:
|
** parameter to help it determine what action to take:
|
||||||
**
|
**
|
||||||
** <table border=1 width=85% align=center>
|
** <table border=1 width=85% align=center>
|
||||||
** <tr><th> createFlag <th> Behavior when page is not already in cache
|
** <tr><th> createFlag <th> Behavior when page is not already in cache
|
||||||
** <tr><td> 0 <td> Do not allocate a new page. Return NULL.
|
** <tr><td> 0 <td> Do not allocate a new page. Return NULL.
|
||||||
** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so.
|
** <tr><td> 1 <td> Allocate a new page if it is easy and convenient to do so.
|
||||||
** Otherwise return NULL.
|
** Otherwise return NULL.
|
||||||
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
||||||
** NULL if allocating a new page is effectively impossible.
|
** NULL if allocating a new page is effectively impossible.
|
||||||
@@ -9588,7 +9588,7 @@ struct sqlite3_pcache_page {
|
|||||||
** as its second argument. If the third parameter, discard, is non-zero,
|
** as its second argument. If the third parameter, discard, is non-zero,
|
||||||
** then the page must be evicted from the cache.
|
** then the page must be evicted from the cache.
|
||||||
** ^If the discard parameter is
|
** ^If the discard parameter is
|
||||||
** zero, then the page may be discarded or retained at the discretion of
|
** zero, then the page may be discarded or retained at the discretion of the
|
||||||
** page cache implementation. ^The page cache implementation
|
** page cache implementation. ^The page cache implementation
|
||||||
** may choose to evict unpinned pages at any time.
|
** may choose to evict unpinned pages at any time.
|
||||||
**
|
**
|
||||||
@@ -9606,7 +9606,7 @@ struct sqlite3_pcache_page {
|
|||||||
** When SQLite calls the xTruncate() method, the cache must discard all
|
** When SQLite calls the xTruncate() method, the cache must discard all
|
||||||
** existing cache entries with page numbers (keys) greater than or equal
|
** existing cache entries with page numbers (keys) greater than or equal
|
||||||
** to the value of the iLimit parameter passed to xTruncate(). If any
|
** to the value of the iLimit parameter passed to xTruncate(). If any
|
||||||
** of these pages are pinned, they are implicitly unpinned, meaning that
|
** of these pages are pinned, they become implicitly unpinned, meaning that
|
||||||
** they can be safely discarded.
|
** they can be safely discarded.
|
||||||
**
|
**
|
||||||
** [[the xDestroy() page cache method]]
|
** [[the xDestroy() page cache method]]
|
||||||
@@ -9905,7 +9905,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||||||
** application receives an SQLITE_LOCKED error, it may call the
|
** application receives an SQLITE_LOCKED error, it may call the
|
||||||
** sqlite3_unlock_notify() method with the blocked connection handle as
|
** sqlite3_unlock_notify() method with the blocked connection handle as
|
||||||
** the first argument to register for a callback that will be invoked
|
** the first argument to register for a callback that will be invoked
|
||||||
** when the blocking connections current transaction is concluded. ^The
|
** when the blocking connection's current transaction is concluded. ^The
|
||||||
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
|
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
|
||||||
** call that concludes the blocking connection's transaction.
|
** call that concludes the blocking connection's transaction.
|
||||||
**
|
**
|
||||||
@@ -9925,7 +9925,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||||||
** blocked connection already has a registered unlock-notify callback,
|
** blocked connection already has a registered unlock-notify callback,
|
||||||
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
||||||
** called with a NULL pointer as its second argument, then any existing
|
** called with a NULL pointer as its second argument, then any existing
|
||||||
** unlock-notify callback is canceled. ^The blocked connections
|
** unlock-notify callback is canceled. ^The blocked connection's
|
||||||
** unlock-notify callback may also be canceled by closing the blocked
|
** unlock-notify callback may also be canceled by closing the blocked
|
||||||
** connection using [sqlite3_close()].
|
** connection using [sqlite3_close()].
|
||||||
**
|
**
|
||||||
@@ -10323,7 +10323,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||||||
** support constraints. In this configuration (which is the default) if
|
** support constraints. In this configuration (which is the default) if
|
||||||
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
||||||
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
||||||
** specified as part of the users SQL statement, regardless of the actual
|
** specified as part of the user's SQL statement, regardless of the actual
|
||||||
** ON CONFLICT mode specified.
|
** ON CONFLICT mode specified.
|
||||||
**
|
**
|
||||||
** If X is non-zero, then the virtual table implementation guarantees
|
** If X is non-zero, then the virtual table implementation guarantees
|
||||||
@@ -10357,7 +10357,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||||||
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
||||||
** <dd>Calls of the form
|
** <dd>Calls of the form
|
||||||
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
||||||
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
|
** [xConnect] or [xCreate] methods of a [virtual table] implementation
|
||||||
** identify that virtual table as being safe to use from within triggers
|
** identify that virtual table as being safe to use from within triggers
|
||||||
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
||||||
** virtual table can do no serious harm even if it is controlled by a
|
** virtual table can do no serious harm even if it is controlled by a
|
||||||
@@ -10525,7 +10525,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||||||
** </table>
|
** </table>
|
||||||
**
|
**
|
||||||
** ^For the purposes of comparing virtual table output values to see if the
|
** ^For the purposes of comparing virtual table output values to see if the
|
||||||
** values are same value for sorting purposes, two NULL values are considered
|
** values are the same value for sorting purposes, two NULL values are considered
|
||||||
** to be the same. In other words, the comparison operator is "IS"
|
** to be the same. In other words, the comparison operator is "IS"
|
||||||
** (or "IS NOT DISTINCT FROM") and not "==".
|
** (or "IS NOT DISTINCT FROM") and not "==".
|
||||||
**
|
**
|
||||||
@@ -10535,7 +10535,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||||||
**
|
**
|
||||||
** ^A virtual table implementation is always free to return rows in any order
|
** ^A virtual table implementation is always free to return rows in any order
|
||||||
** it wants, as long as the "orderByConsumed" flag is not set. ^When the
|
** it wants, as long as the "orderByConsumed" flag is not set. ^When the
|
||||||
** the "orderByConsumed" flag is unset, the query planner will add extra
|
** "orderByConsumed" flag is unset, the query planner will add extra
|
||||||
** [bytecode] to ensure that the final results returned by the SQL query are
|
** [bytecode] to ensure that the final results returned by the SQL query are
|
||||||
** ordered correctly. The use of the "orderByConsumed" flag and the
|
** ordered correctly. The use of the "orderByConsumed" flag and the
|
||||||
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
||||||
@@ -10632,7 +10632,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
|
|||||||
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
|
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
|
||||||
** xFilter method which invokes these routines, and specifically
|
** xFilter method which invokes these routines, and specifically
|
||||||
** a parameter that was previously selected for all-at-once IN constraint
|
** a parameter that was previously selected for all-at-once IN constraint
|
||||||
** processing use the [sqlite3_vtab_in()] interface in the
|
** processing using the [sqlite3_vtab_in()] interface in the
|
||||||
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
|
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
|
||||||
** an xFilter argument that was selected for all-at-once IN constraint
|
** an xFilter argument that was selected for all-at-once IN constraint
|
||||||
** processing, then these routines return [SQLITE_ERROR].)^
|
** processing, then these routines return [SQLITE_ERROR].)^
|
||||||
@@ -10687,7 +10687,7 @@ SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut);
|
|||||||
** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V)
|
** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V)
|
||||||
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
||||||
** constraint is not available. ^The sqlite3_vtab_rhs_value() interface
|
** constraint is not available. ^The sqlite3_vtab_rhs_value() interface
|
||||||
** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
||||||
** something goes wrong.
|
** something goes wrong.
|
||||||
**
|
**
|
||||||
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
||||||
@@ -10715,8 +10715,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** KEYWORDS: {conflict resolution mode}
|
** KEYWORDS: {conflict resolution mode}
|
||||||
**
|
**
|
||||||
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
||||||
** inform a [virtual table] implementation what the [ON CONFLICT] mode
|
** inform a [virtual table] implementation of the [ON CONFLICT] mode
|
||||||
** is for the SQL statement being evaluated.
|
** for the SQL statement being evaluated.
|
||||||
**
|
**
|
||||||
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
||||||
** return value from the [sqlite3_set_authorizer()] callback and that
|
** return value from the [sqlite3_set_authorizer()] callback and that
|
||||||
@@ -10756,39 +10756,39 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
||||||
** <dd>^The "double" variable pointed to by the V parameter will be set to the
|
** <dd>^The "double" variable pointed to by the V parameter will be set to the
|
||||||
** query planner's estimate for the average number of rows output from each
|
** query planner's estimate for the average number of rows output from each
|
||||||
** iteration of the X-th loop. If the query planner's estimates was accurate,
|
** iteration of the X-th loop. If the query planner's estimate was accurate,
|
||||||
** then this value will approximate the quotient NVISIT/NLOOP and the
|
** then this value will approximate the quotient NVISIT/NLOOP and the
|
||||||
** product of this value for all prior loops with the same SELECTID will
|
** product of this value for all prior loops with the same SELECTID will
|
||||||
** be the NLOOP value for the current loop.
|
** be the NLOOP value for the current loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
|
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
|
||||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||||
** to a zero-terminated UTF-8 string containing the name of the index or table
|
** to a zero-terminated UTF-8 string containing the name of the index or table
|
||||||
** used for the X-th loop.
|
** used for the X-th loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
||||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||||
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
|
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
|
||||||
** description for the X-th loop.
|
** description for the X-th loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
||||||
** <dd>^The "int" variable pointed to by the V parameter will be set to the
|
** <dd>^The "int" variable pointed to by the V parameter will be set to the
|
||||||
** id for the X-th query plan element. The id value is unique within the
|
** id for the X-th query plan element. The id value is unique within the
|
||||||
** statement. The select-id is the same value as is output in the first
|
** statement. The select-id is the same value as is output in the first
|
||||||
** column of an [EXPLAIN QUERY PLAN] query.
|
** column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
|
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
|
||||||
** <dd>The "int" variable pointed to by the V parameter will be set to the
|
** <dd>The "int" variable pointed to by the V parameter will be set to the
|
||||||
** the id of the parent of the current query element, if applicable, or
|
** id of the parent of the current query element, if applicable, or
|
||||||
** to zero if the query element has no parent. This is the same value as
|
** to zero if the query element has no parent. This is the same value as
|
||||||
** returned in the second column of an [EXPLAIN QUERY PLAN] query.
|
** returned in the second column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
|
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
|
||||||
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
||||||
** according to the processor time-stamp counter, that elapsed while the
|
** according to the processor time-stamp counter, that elapsed while the
|
||||||
** query element was being processed. This value is not available for
|
** query element was being processed. This value is not available for
|
||||||
** all query elements - if it is unavailable the output variable is
|
** all query elements - if it is unavailable the output variable is
|
||||||
** set to -1.
|
** set to -1.</dd>
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_SCANSTAT_NLOOP 0
|
#define SQLITE_SCANSTAT_NLOOP 0
|
||||||
@@ -10829,8 +10829,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
||||||
**
|
**
|
||||||
** Parameter "idx" identifies the specific query element to retrieve statistics
|
** Parameter "idx" identifies the specific query element to retrieve statistics
|
||||||
** for. Query elements are numbered starting from zero. A value of -1 may be
|
** for. Query elements are numbered starting from zero. A value of -1 may
|
||||||
** to query for statistics regarding the entire query. ^If idx is out of range
|
** retrieve statistics for the entire query. ^If idx is out of range
|
||||||
** - less than -1 or greater than or equal to the total number of query
|
** - less than -1 or greater than or equal to the total number of query
|
||||||
** elements used to implement the statement - a non-zero value is returned and
|
** elements used to implement the statement - a non-zero value is returned and
|
||||||
** the variable that pOut points to is unchanged.
|
** the variable that pOut points to is unchanged.
|
||||||
@@ -10987,8 +10987,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
|
|||||||
** triggers; and so forth.
|
** triggers; and so forth.
|
||||||
**
|
**
|
||||||
** When the [sqlite3_blob_write()] API is used to update a blob column,
|
** When the [sqlite3_blob_write()] API is used to update a blob column,
|
||||||
** the pre-update hook is invoked with SQLITE_DELETE. This is because the
|
** the pre-update hook is invoked with SQLITE_DELETE, because
|
||||||
** in this case the new values are not available. In this case, when a
|
** the new values are not yet available. In this case, when a
|
||||||
** callback made with op==SQLITE_DELETE is actually a write using the
|
** callback made with op==SQLITE_DELETE is actually a write using the
|
||||||
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
||||||
** the index of the column being written. In other cases, where the
|
** the index of the column being written. In other cases, where the
|
||||||
@@ -11241,7 +11241,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||||||
** For an ordinary on-disk database file, the serialization is just a
|
** For an ordinary on-disk database file, the serialization is just a
|
||||||
** copy of the disk file. For an in-memory database or a "TEMP" database,
|
** copy of the disk file. For an in-memory database or a "TEMP" database,
|
||||||
** the serialization is the same sequence of bytes which would be written
|
** the serialization is the same sequence of bytes which would be written
|
||||||
** to disk if that database where backed up to disk.
|
** to disk if that database were backed up to disk.
|
||||||
**
|
**
|
||||||
** The usual case is that sqlite3_serialize() copies the serialization of
|
** The usual case is that sqlite3_serialize() copies the serialization of
|
||||||
** the database into memory obtained from [sqlite3_malloc64()] and returns
|
** the database into memory obtained from [sqlite3_malloc64()] and returns
|
||||||
@@ -11250,7 +11250,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||||||
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
||||||
** are made, and the sqlite3_serialize() function will return a pointer
|
** are made, and the sqlite3_serialize() function will return a pointer
|
||||||
** to the contiguous memory representation of the database that SQLite
|
** to the contiguous memory representation of the database that SQLite
|
||||||
** is currently using for that database, or NULL if the no such contiguous
|
** is currently using for that database, or NULL if no such contiguous
|
||||||
** memory representation of the database exists. A contiguous memory
|
** memory representation of the database exists. A contiguous memory
|
||||||
** representation of the database will usually only exist if there has
|
** representation of the database will usually only exist if there has
|
||||||
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
||||||
@@ -11321,7 +11321,7 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
|||||||
** database is currently in a read transaction or is involved in a backup
|
** database is currently in a read transaction or is involved in a backup
|
||||||
** operation.
|
** operation.
|
||||||
**
|
**
|
||||||
** It is not possible to deserialized into the TEMP database. If the
|
** It is not possible to deserialize into the TEMP database. If the
|
||||||
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
|
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
|
||||||
** function returns SQLITE_ERROR.
|
** function returns SQLITE_ERROR.
|
||||||
**
|
**
|
||||||
@@ -11343,7 +11343,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||||||
sqlite3 *db, /* The database connection */
|
sqlite3 *db, /* The database connection */
|
||||||
const char *zSchema, /* Which DB to reopen with the deserialization */
|
const char *zSchema, /* Which DB to reopen with the deserialization */
|
||||||
unsigned char *pData, /* The serialized database content */
|
unsigned char *pData, /* The serialized database content */
|
||||||
sqlite3_int64 szDb, /* Number bytes in the deserialization */
|
sqlite3_int64 szDb, /* Number of bytes in the deserialization */
|
||||||
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
|
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
|
||||||
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
||||||
);
|
);
|
||||||
@@ -11351,7 +11351,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||||||
/*
|
/*
|
||||||
** CAPI3REF: Flags for sqlite3_deserialize()
|
** CAPI3REF: Flags for sqlite3_deserialize()
|
||||||
**
|
**
|
||||||
** The following are allowed values for 6th argument (the F argument) to
|
** The following are allowed values for the 6th argument (the F argument) to
|
||||||
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
|
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
|
||||||
**
|
**
|
||||||
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
||||||
@@ -19168,7 +19168,6 @@ struct Index {
|
|||||||
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
|
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
|
||||||
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
|
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
|
||||||
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
|
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
|
||||||
unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */
|
|
||||||
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
|
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
|
||||||
unsigned bHasExpr:1; /* Index contains an expression, either a literal
|
unsigned bHasExpr:1; /* Index contains an expression, either a literal
|
||||||
** expression, or a reference to a VIRTUAL column */
|
** expression, or a reference to a VIRTUAL column */
|
||||||
@@ -19441,6 +19440,7 @@ struct Expr {
|
|||||||
Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL
|
Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL
|
||||||
** for a column of an index on an expression */
|
** for a column of an index on an expression */
|
||||||
Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */
|
Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */
|
||||||
|
int nReg; /* TK_NULLS: Number of registers to NULL out */
|
||||||
struct { /* TK_IN, TK_SELECT, and TK_EXISTS */
|
struct { /* TK_IN, TK_SELECT, and TK_EXISTS */
|
||||||
int iAddr; /* Subroutine entry address */
|
int iAddr; /* Subroutine entry address */
|
||||||
int regReturn; /* Register used to hold return address */
|
int regReturn; /* Register used to hold return address */
|
||||||
@@ -21475,6 +21475,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int)
|
|||||||
SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int);
|
SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int);
|
||||||
SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
|
SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
|
||||||
SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int);
|
SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int);
|
||||||
|
SQLITE_PRIVATE void sqlite3ExprNullRegisterRange(Parse*, int, int);
|
||||||
SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
|
SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
|
||||||
SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
|
SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
|
||||||
SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
|
SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
|
||||||
@@ -111488,7 +111489,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){
|
|||||||
return pLeft;
|
return pLeft;
|
||||||
}else{
|
}else{
|
||||||
u32 f = pLeft->flags | pRight->flags;
|
u32 f = pLeft->flags | pRight->flags;
|
||||||
if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse))==EP_IsFalse
|
if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse|EP_HasFunc))==EP_IsFalse
|
||||||
&& !IN_RENAME_OBJECT
|
&& !IN_RENAME_OBJECT
|
||||||
){
|
){
|
||||||
sqlite3ExprDeferredDelete(pParse, pLeft);
|
sqlite3ExprDeferredDelete(pParse, pLeft);
|
||||||
@@ -115242,6 +115243,12 @@ expr_code_doover:
|
|||||||
sqlite3VdbeLoadString(v, target, pExpr->u.zToken);
|
sqlite3VdbeLoadString(v, target, pExpr->u.zToken);
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
case TK_NULLS: {
|
||||||
|
/* Set a range of registers to NULL. pExpr->y.nReg registers starting
|
||||||
|
** with target */
|
||||||
|
sqlite3VdbeAddOp3(v, OP_Null, 0, target, target + pExpr->y.nReg - 1);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
/* Make NULL the default case so that if a bug causes an illegal
|
/* Make NULL the default case so that if a bug causes an illegal
|
||||||
** Expr node to be passed into this function, it will be handled
|
** Expr node to be passed into this function, it will be handled
|
||||||
@@ -115926,6 +115933,25 @@ SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(
|
|||||||
return regDest;
|
return regDest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Make arrangements to invoke OP_Null on a range of registers
|
||||||
|
** during initialization.
|
||||||
|
*/
|
||||||
|
SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3ExprNullRegisterRange(
|
||||||
|
Parse *pParse, /* Parsing context */
|
||||||
|
int iReg, /* First register to set to NULL */
|
||||||
|
int nReg /* Number of sequential registers to NULL out */
|
||||||
|
){
|
||||||
|
u8 okConstFactor = pParse->okConstFactor;
|
||||||
|
Expr t;
|
||||||
|
memset(&t, 0, sizeof(t));
|
||||||
|
t.op = TK_NULLS;
|
||||||
|
t.y.nReg = nReg;
|
||||||
|
pParse->okConstFactor = 1;
|
||||||
|
sqlite3ExprCodeRunJustOnce(pParse, &t, iReg);
|
||||||
|
pParse->okConstFactor = okConstFactor;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Generate code to evaluate an expression and store the results
|
** Generate code to evaluate an expression and store the results
|
||||||
** into a register. Return the register number where the results
|
** into a register. Return the register number where the results
|
||||||
@@ -127196,7 +127222,6 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
|
|||||||
assert( j<=0x7fff );
|
assert( j<=0x7fff );
|
||||||
if( j<0 ){
|
if( j<0 ){
|
||||||
j = pTab->iPKey;
|
j = pTab->iPKey;
|
||||||
pIndex->bIdxRowid = 1;
|
|
||||||
}else{
|
}else{
|
||||||
if( pTab->aCol[j].notNull==0 ){
|
if( pTab->aCol[j].notNull==0 ){
|
||||||
pIndex->uniqNotNull = 0;
|
pIndex->uniqNotNull = 0;
|
||||||
@@ -153177,6 +153202,7 @@ SQLITE_PRIVATE int sqlite3Select(
|
|||||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag);
|
sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag);
|
||||||
VdbeComment((v, "clear abort flag"));
|
VdbeComment((v, "clear abort flag"));
|
||||||
sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1);
|
sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1);
|
||||||
|
sqlite3ExprNullRegisterRange(pParse, iAMem, pGroupBy->nExpr);
|
||||||
|
|
||||||
/* Begin a loop that will extract all source rows in GROUP BY order.
|
/* Begin a loop that will extract all source rows in GROUP BY order.
|
||||||
** This might involve two separate loops with an OP_Sort in between, or
|
** This might involve two separate loops with an OP_Sort in between, or
|
||||||
@@ -160173,7 +160199,9 @@ static Expr *removeUnindexableInClauseTerms(
|
|||||||
int iField;
|
int iField;
|
||||||
assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 );
|
assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 );
|
||||||
iField = pLoop->aLTerm[i]->u.x.iField - 1;
|
iField = pLoop->aLTerm[i]->u.x.iField - 1;
|
||||||
if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */
|
if( NEVER(pOrigRhs->a[iField].pExpr==0) ){
|
||||||
|
continue; /* Duplicate PK column */
|
||||||
|
}
|
||||||
pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
|
pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
|
||||||
pOrigRhs->a[iField].pExpr = 0;
|
pOrigRhs->a[iField].pExpr = 0;
|
||||||
if( pRhs ) pRhs->a[pRhs->nExpr-1].u.x.iOrderByCol = iField+1;
|
if( pRhs ) pRhs->a[pRhs->nExpr-1].u.x.iOrderByCol = iField+1;
|
||||||
@@ -160270,7 +160298,7 @@ static SQLITE_NOINLINE void codeINTerm(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(i=iEq;i<pLoop->nLTerm; i++){
|
for(i=iEq; i<pLoop->nLTerm; i++){
|
||||||
assert( pLoop->aLTerm[i]!=0 );
|
assert( pLoop->aLTerm[i]!=0 );
|
||||||
if( pLoop->aLTerm[i]->pExpr==pX ) nEq++;
|
if( pLoop->aLTerm[i]->pExpr==pX ) nEq++;
|
||||||
}
|
}
|
||||||
@@ -160279,22 +160307,13 @@ static SQLITE_NOINLINE void codeINTerm(
|
|||||||
if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){
|
if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){
|
||||||
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab);
|
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab);
|
||||||
}else{
|
}else{
|
||||||
Expr *pExpr = pTerm->pExpr;
|
sqlite3 *db = pParse->db;
|
||||||
if( pExpr->iTable==0 || !ExprHasProperty(pExpr, EP_Subrtn) ){
|
Expr *pXMod = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
|
||||||
sqlite3 *db = pParse->db;
|
if( !db->mallocFailed ){
|
||||||
pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
|
aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nEq);
|
||||||
if( !db->mallocFailed ){
|
eType = sqlite3FindInIndex(pParse, pXMod, IN_INDEX_LOOP, 0, aiMap, &iTab);
|
||||||
aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
|
|
||||||
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap,&iTab);
|
|
||||||
pExpr->iTable = iTab;
|
|
||||||
}
|
|
||||||
sqlite3ExprDelete(db, pX);
|
|
||||||
}else{
|
|
||||||
int n = sqlite3ExprVectorSize(pX->pLeft);
|
|
||||||
aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n));
|
|
||||||
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab);
|
|
||||||
}
|
}
|
||||||
pX = pExpr;
|
sqlite3ExprDelete(db, pXMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
if( eType==IN_INDEX_INDEX_DESC ){
|
if( eType==IN_INDEX_INDEX_DESC ){
|
||||||
@@ -160324,7 +160343,7 @@ static SQLITE_NOINLINE void codeINTerm(
|
|||||||
if( pIn ){
|
if( pIn ){
|
||||||
int iMap = 0; /* Index in aiMap[] */
|
int iMap = 0; /* Index in aiMap[] */
|
||||||
pIn += i;
|
pIn += i;
|
||||||
for(i=iEq;i<pLoop->nLTerm; i++){
|
for(i=iEq; i<pLoop->nLTerm; i++){
|
||||||
if( pLoop->aLTerm[i]->pExpr==pX ){
|
if( pLoop->aLTerm[i]->pExpr==pX ){
|
||||||
int iOut = iTarget + i - iEq;
|
int iOut = iTarget + i - iEq;
|
||||||
if( eType==IN_INDEX_ROWID ){
|
if( eType==IN_INDEX_ROWID ){
|
||||||
@@ -167682,6 +167701,7 @@ static int whereLoopAddBtreeIndex(
|
|||||||
if( ExprUseXSelect(pExpr) ){
|
if( ExprUseXSelect(pExpr) ){
|
||||||
/* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */
|
/* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */
|
||||||
int i;
|
int i;
|
||||||
|
int bRedundant = 0;
|
||||||
nIn = 46; assert( 46==sqlite3LogEst(25) );
|
nIn = 46; assert( 46==sqlite3LogEst(25) );
|
||||||
|
|
||||||
/* The expression may actually be of the form (x, y) IN (SELECT...).
|
/* The expression may actually be of the form (x, y) IN (SELECT...).
|
||||||
@@ -167690,7 +167710,20 @@ static int whereLoopAddBtreeIndex(
|
|||||||
** for each such term. The following loop checks that pTerm is the
|
** for each such term. The following loop checks that pTerm is the
|
||||||
** first such term in use, and sets nIn back to 0 if it is not. */
|
** first such term in use, and sets nIn back to 0 if it is not. */
|
||||||
for(i=0; i<pNew->nLTerm-1; i++){
|
for(i=0; i<pNew->nLTerm-1; i++){
|
||||||
if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0;
|
if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ){
|
||||||
|
nIn = 0;
|
||||||
|
if( pNew->aLTerm[i]->u.x.iField == pTerm->u.x.iField ){
|
||||||
|
/* Detect when two or more columns of an index match the same
|
||||||
|
** column of a vector IN operater, and avoid adding the column
|
||||||
|
** to the WhereLoop more than once. See tag-20250707-01
|
||||||
|
** in test/rowvalue.test */
|
||||||
|
bRedundant = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( bRedundant ){
|
||||||
|
pNew->nLTerm--;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
|
}else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
|
||||||
/* "x IN (value, value, ...)" */
|
/* "x IN (value, value, ...)" */
|
||||||
@@ -167922,7 +167955,7 @@ static int whereLoopAddBtreeIndex(
|
|||||||
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
|
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
|
||||||
&& pNew->u.btree.nEq<pProbe->nColumn
|
&& pNew->u.btree.nEq<pProbe->nColumn
|
||||||
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
|
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
|
||||||
(pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid))
|
pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
|
||||||
){
|
){
|
||||||
if( pNew->u.btree.nEq>3 ){
|
if( pNew->u.btree.nEq>3 ){
|
||||||
sqlite3ProgressCheck(pParse);
|
sqlite3ProgressCheck(pParse);
|
||||||
@@ -168465,6 +168498,7 @@ static int whereLoopAddBtree(
|
|||||||
pNew->u.btree.nEq = 0;
|
pNew->u.btree.nEq = 0;
|
||||||
pNew->u.btree.nBtm = 0;
|
pNew->u.btree.nBtm = 0;
|
||||||
pNew->u.btree.nTop = 0;
|
pNew->u.btree.nTop = 0;
|
||||||
|
pNew->u.btree.nDistinctCol = 0;
|
||||||
pNew->nSkip = 0;
|
pNew->nSkip = 0;
|
||||||
pNew->nLTerm = 0;
|
pNew->nLTerm = 0;
|
||||||
pNew->iSortIdx = 0;
|
pNew->iSortIdx = 0;
|
||||||
@@ -169533,8 +169567,6 @@ static i8 wherePathSatisfiesOrderBy(
|
|||||||
obSat = obDone;
|
obSat = obDone;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}else if( wctrlFlags & WHERE_DISTINCTBY ){
|
|
||||||
pLoop->u.btree.nDistinctCol = 0;
|
|
||||||
}
|
}
|
||||||
iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor;
|
iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor;
|
||||||
|
|
||||||
@@ -179897,12 +179929,21 @@ static YYACTIONTYPE yy_reduce(
|
|||||||
** expr1 IN ()
|
** expr1 IN ()
|
||||||
** expr1 NOT IN ()
|
** expr1 NOT IN ()
|
||||||
**
|
**
|
||||||
** simplify to constants 0 (false) and 1 (true), respectively,
|
** simplify to constants 0 (false) and 1 (true), respectively.
|
||||||
** regardless of the value of expr1.
|
**
|
||||||
|
** Except, do not apply this optimization if expr1 contains a function
|
||||||
|
** because that function might be an aggregate (we don't know yet whether
|
||||||
|
** it is or not) and if it is an aggregate, that could change the meaning
|
||||||
|
** of the whole query.
|
||||||
*/
|
*/
|
||||||
sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy590);
|
Expr *pB = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy502 ? "true" : "false");
|
||||||
yymsp[-4].minor.yy590 = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy502 ? "true" : "false");
|
if( pB ) sqlite3ExprIdToTrueFalse(pB);
|
||||||
if( yymsp[-4].minor.yy590 ) sqlite3ExprIdToTrueFalse(yymsp[-4].minor.yy590);
|
if( !ExprHasProperty(yymsp[-4].minor.yy590, EP_HasFunc) ){
|
||||||
|
sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy590);
|
||||||
|
yymsp[-4].minor.yy590 = pB;
|
||||||
|
}else{
|
||||||
|
yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, yymsp[-3].minor.yy502 ? TK_OR : TK_AND, pB, yymsp[-4].minor.yy590);
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
Expr *pRHS = yymsp[-1].minor.yy402->a[0].pExpr;
|
Expr *pRHS = yymsp[-1].minor.yy402->a[0].pExpr;
|
||||||
if( yymsp[-1].minor.yy402->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && yymsp[-4].minor.yy590->op!=TK_VECTOR ){
|
if( yymsp[-1].minor.yy402->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && yymsp[-4].minor.yy590->op!=TK_VECTOR ){
|
||||||
@@ -181508,7 +181549,7 @@ static int getToken(const unsigned char **pz){
|
|||||||
int t; /* Token type to return */
|
int t; /* Token type to return */
|
||||||
do {
|
do {
|
||||||
z += sqlite3GetToken(z, &t);
|
z += sqlite3GetToken(z, &t);
|
||||||
}while( t==TK_SPACE );
|
}while( t==TK_SPACE || t==TK_COMMENT );
|
||||||
if( t==TK_ID
|
if( t==TK_ID
|
||||||
|| t==TK_STRING
|
|| t==TK_STRING
|
||||||
|| t==TK_JOIN_KW
|
|| t==TK_JOIN_KW
|
||||||
@@ -246163,9 +246204,9 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){
|
|||||||
** leave an error in the Fts5Index object.
|
** leave an error in the Fts5Index object.
|
||||||
*/
|
*/
|
||||||
static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
|
static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
|
||||||
const int nTomb = pIter->pSeg->nPgTombstone;
|
const i64 nTomb = (i64)pIter->pSeg->nPgTombstone;
|
||||||
if( nTomb>0 ){
|
if( nTomb>0 ){
|
||||||
int nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1);
|
i64 nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1);
|
||||||
Fts5TombstoneArray *pNew;
|
Fts5TombstoneArray *pNew;
|
||||||
pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte);
|
pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte);
|
||||||
if( pNew ){
|
if( pNew ){
|
||||||
@@ -257266,7 +257307,7 @@ static void fts5SourceIdFunc(
|
|||||||
){
|
){
|
||||||
assert( nArg==0 );
|
assert( nArg==0 );
|
||||||
UNUSED_PARAM2(nArg, apUnused);
|
UNUSED_PARAM2(nArg, apUnused);
|
||||||
sqlite3_result_text(pCtx, "fts5: 2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079", -1, SQLITE_TRANSIENT);
|
sqlite3_result_text(pCtx, "fts5: 2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3", -1, SQLITE_TRANSIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
90
deps/sqlite/sqlite3.h
vendored
90
deps/sqlite/sqlite3.h
vendored
@@ -146,9 +146,9 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.50.2"
|
#define SQLITE_VERSION "3.50.4"
|
||||||
#define SQLITE_VERSION_NUMBER 3050002
|
#define SQLITE_VERSION_NUMBER 3050004
|
||||||
#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079"
|
#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@@ -9058,13 +9058,13 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
||||||
** <dd>^This is the number of sort operations that have occurred.
|
** <dd>^This is the number of sort operations that have occurred.
|
||||||
** A non-zero value in this counter may indicate an opportunity to
|
** A non-zero value in this counter may indicate an opportunity to
|
||||||
** improvement performance through careful use of indices.</dd>
|
** improve performance through careful use of indices.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
|
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
|
||||||
** <dd>^This is the number of rows inserted into transient indices that
|
** <dd>^This is the number of rows inserted into transient indices that
|
||||||
** were created automatically in order to help joins run faster.
|
** were created automatically in order to help joins run faster.
|
||||||
** A non-zero value in this counter may indicate an opportunity to
|
** A non-zero value in this counter may indicate an opportunity to
|
||||||
** improvement performance by adding permanent indices that do not
|
** improve performance by adding permanent indices that do not
|
||||||
** need to be reinitialized each time the statement is run.</dd>
|
** need to be reinitialized each time the statement is run.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
||||||
@@ -9073,19 +9073,19 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** to 2147483647. The number of virtual machine operations can be
|
** to 2147483647. The number of virtual machine operations can be
|
||||||
** used as a proxy for the total work done by the prepared statement.
|
** used as a proxy for the total work done by the prepared statement.
|
||||||
** If the number of virtual machine operations exceeds 2147483647
|
** If the number of virtual machine operations exceeds 2147483647
|
||||||
** then the value returned by this statement status code is undefined.
|
** then the value returned by this statement status code is undefined.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
|
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
|
||||||
** <dd>^This is the number of times that the prepare statement has been
|
** <dd>^This is the number of times that the prepare statement has been
|
||||||
** automatically regenerated due to schema changes or changes to
|
** automatically regenerated due to schema changes or changes to
|
||||||
** [bound parameters] that might affect the query plan.
|
** [bound parameters] that might affect the query plan.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
|
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
|
||||||
** <dd>^This is the number of times that the prepared statement has
|
** <dd>^This is the number of times that the prepared statement has
|
||||||
** been run. A single "run" for the purposes of this counter is one
|
** been run. A single "run" for the purposes of this counter is one
|
||||||
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
|
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
|
||||||
** The counter is incremented on the first [sqlite3_step()] call of each
|
** The counter is incremented on the first [sqlite3_step()] call of each
|
||||||
** cycle.
|
** cycle.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
||||||
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
||||||
@@ -9095,7 +9095,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** step was bypassed because a Bloom filter returned not-found. The
|
** step was bypassed because a Bloom filter returned not-found. The
|
||||||
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
||||||
** times that the Bloom filter returned a find, and thus the join step
|
** times that the Bloom filter returned a find, and thus the join step
|
||||||
** had to be processed as normal.
|
** had to be processed as normal.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
|
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
|
||||||
** <dd>^This is the approximate number of bytes of heap memory
|
** <dd>^This is the approximate number of bytes of heap memory
|
||||||
@@ -9200,9 +9200,9 @@ struct sqlite3_pcache_page {
|
|||||||
** SQLite will typically create one cache instance for each open database file,
|
** SQLite will typically create one cache instance for each open database file,
|
||||||
** though this is not guaranteed. ^The
|
** though this is not guaranteed. ^The
|
||||||
** first parameter, szPage, is the size in bytes of the pages that must
|
** first parameter, szPage, is the size in bytes of the pages that must
|
||||||
** be allocated by the cache. ^szPage will always a power of two. ^The
|
** be allocated by the cache. ^szPage will always be a power of two. ^The
|
||||||
** second parameter szExtra is a number of bytes of extra storage
|
** second parameter szExtra is a number of bytes of extra storage
|
||||||
** associated with each page cache entry. ^The szExtra parameter will
|
** associated with each page cache entry. ^The szExtra parameter will be
|
||||||
** a number less than 250. SQLite will use the
|
** a number less than 250. SQLite will use the
|
||||||
** extra szExtra bytes on each page to store metadata about the underlying
|
** extra szExtra bytes on each page to store metadata about the underlying
|
||||||
** database page on disk. The value passed into szExtra depends
|
** database page on disk. The value passed into szExtra depends
|
||||||
@@ -9210,17 +9210,17 @@ struct sqlite3_pcache_page {
|
|||||||
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
|
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
|
||||||
** created will be used to cache database pages of a file stored on disk, or
|
** created will be used to cache database pages of a file stored on disk, or
|
||||||
** false if it is used for an in-memory database. The cache implementation
|
** false if it is used for an in-memory database. The cache implementation
|
||||||
** does not have to do anything special based with the value of bPurgeable;
|
** does not have to do anything special based upon the value of bPurgeable;
|
||||||
** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will
|
** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will
|
||||||
** never invoke xUnpin() except to deliberately delete a page.
|
** never invoke xUnpin() except to deliberately delete a page.
|
||||||
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
||||||
** false will always have the "discard" flag set to true.
|
** false will always have the "discard" flag set to true.
|
||||||
** ^Hence, a cache created with bPurgeable false will
|
** ^Hence, a cache created with bPurgeable set to false will
|
||||||
** never contain any unpinned pages.
|
** never contain any unpinned pages.
|
||||||
**
|
**
|
||||||
** [[the xCachesize() page cache method]]
|
** [[the xCachesize() page cache method]]
|
||||||
** ^(The xCachesize() method may be called at any time by SQLite to set the
|
** ^(The xCachesize() method may be called at any time by SQLite to set the
|
||||||
** suggested maximum cache-size (number of pages stored by) the cache
|
** suggested maximum cache-size (number of pages stored) for the cache
|
||||||
** instance passed as the first argument. This is the value configured using
|
** instance passed as the first argument. This is the value configured using
|
||||||
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
||||||
** parameter, the implementation is not required to do anything with this
|
** parameter, the implementation is not required to do anything with this
|
||||||
@@ -9247,12 +9247,12 @@ struct sqlite3_pcache_page {
|
|||||||
** implementation must return a pointer to the page buffer with its content
|
** implementation must return a pointer to the page buffer with its content
|
||||||
** intact. If the requested page is not already in the cache, then the
|
** intact. If the requested page is not already in the cache, then the
|
||||||
** cache implementation should use the value of the createFlag
|
** cache implementation should use the value of the createFlag
|
||||||
** parameter to help it determined what action to take:
|
** parameter to help it determine what action to take:
|
||||||
**
|
**
|
||||||
** <table border=1 width=85% align=center>
|
** <table border=1 width=85% align=center>
|
||||||
** <tr><th> createFlag <th> Behavior when page is not already in cache
|
** <tr><th> createFlag <th> Behavior when page is not already in cache
|
||||||
** <tr><td> 0 <td> Do not allocate a new page. Return NULL.
|
** <tr><td> 0 <td> Do not allocate a new page. Return NULL.
|
||||||
** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so.
|
** <tr><td> 1 <td> Allocate a new page if it is easy and convenient to do so.
|
||||||
** Otherwise return NULL.
|
** Otherwise return NULL.
|
||||||
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
||||||
** NULL if allocating a new page is effectively impossible.
|
** NULL if allocating a new page is effectively impossible.
|
||||||
@@ -9269,7 +9269,7 @@ struct sqlite3_pcache_page {
|
|||||||
** as its second argument. If the third parameter, discard, is non-zero,
|
** as its second argument. If the third parameter, discard, is non-zero,
|
||||||
** then the page must be evicted from the cache.
|
** then the page must be evicted from the cache.
|
||||||
** ^If the discard parameter is
|
** ^If the discard parameter is
|
||||||
** zero, then the page may be discarded or retained at the discretion of
|
** zero, then the page may be discarded or retained at the discretion of the
|
||||||
** page cache implementation. ^The page cache implementation
|
** page cache implementation. ^The page cache implementation
|
||||||
** may choose to evict unpinned pages at any time.
|
** may choose to evict unpinned pages at any time.
|
||||||
**
|
**
|
||||||
@@ -9287,7 +9287,7 @@ struct sqlite3_pcache_page {
|
|||||||
** When SQLite calls the xTruncate() method, the cache must discard all
|
** When SQLite calls the xTruncate() method, the cache must discard all
|
||||||
** existing cache entries with page numbers (keys) greater than or equal
|
** existing cache entries with page numbers (keys) greater than or equal
|
||||||
** to the value of the iLimit parameter passed to xTruncate(). If any
|
** to the value of the iLimit parameter passed to xTruncate(). If any
|
||||||
** of these pages are pinned, they are implicitly unpinned, meaning that
|
** of these pages are pinned, they become implicitly unpinned, meaning that
|
||||||
** they can be safely discarded.
|
** they can be safely discarded.
|
||||||
**
|
**
|
||||||
** [[the xDestroy() page cache method]]
|
** [[the xDestroy() page cache method]]
|
||||||
@@ -9586,7 +9586,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||||||
** application receives an SQLITE_LOCKED error, it may call the
|
** application receives an SQLITE_LOCKED error, it may call the
|
||||||
** sqlite3_unlock_notify() method with the blocked connection handle as
|
** sqlite3_unlock_notify() method with the blocked connection handle as
|
||||||
** the first argument to register for a callback that will be invoked
|
** the first argument to register for a callback that will be invoked
|
||||||
** when the blocking connections current transaction is concluded. ^The
|
** when the blocking connection's current transaction is concluded. ^The
|
||||||
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
|
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
|
||||||
** call that concludes the blocking connection's transaction.
|
** call that concludes the blocking connection's transaction.
|
||||||
**
|
**
|
||||||
@@ -9606,7 +9606,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||||||
** blocked connection already has a registered unlock-notify callback,
|
** blocked connection already has a registered unlock-notify callback,
|
||||||
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
||||||
** called with a NULL pointer as its second argument, then any existing
|
** called with a NULL pointer as its second argument, then any existing
|
||||||
** unlock-notify callback is canceled. ^The blocked connections
|
** unlock-notify callback is canceled. ^The blocked connection's
|
||||||
** unlock-notify callback may also be canceled by closing the blocked
|
** unlock-notify callback may also be canceled by closing the blocked
|
||||||
** connection using [sqlite3_close()].
|
** connection using [sqlite3_close()].
|
||||||
**
|
**
|
||||||
@@ -10004,7 +10004,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||||||
** support constraints. In this configuration (which is the default) if
|
** support constraints. In this configuration (which is the default) if
|
||||||
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
||||||
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
||||||
** specified as part of the users SQL statement, regardless of the actual
|
** specified as part of the user's SQL statement, regardless of the actual
|
||||||
** ON CONFLICT mode specified.
|
** ON CONFLICT mode specified.
|
||||||
**
|
**
|
||||||
** If X is non-zero, then the virtual table implementation guarantees
|
** If X is non-zero, then the virtual table implementation guarantees
|
||||||
@@ -10038,7 +10038,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||||||
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
||||||
** <dd>Calls of the form
|
** <dd>Calls of the form
|
||||||
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
||||||
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
|
** [xConnect] or [xCreate] methods of a [virtual table] implementation
|
||||||
** identify that virtual table as being safe to use from within triggers
|
** identify that virtual table as being safe to use from within triggers
|
||||||
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
||||||
** virtual table can do no serious harm even if it is controlled by a
|
** virtual table can do no serious harm even if it is controlled by a
|
||||||
@@ -10206,7 +10206,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||||||
** </table>
|
** </table>
|
||||||
**
|
**
|
||||||
** ^For the purposes of comparing virtual table output values to see if the
|
** ^For the purposes of comparing virtual table output values to see if the
|
||||||
** values are same value for sorting purposes, two NULL values are considered
|
** values are the same value for sorting purposes, two NULL values are considered
|
||||||
** to be the same. In other words, the comparison operator is "IS"
|
** to be the same. In other words, the comparison operator is "IS"
|
||||||
** (or "IS NOT DISTINCT FROM") and not "==".
|
** (or "IS NOT DISTINCT FROM") and not "==".
|
||||||
**
|
**
|
||||||
@@ -10216,7 +10216,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||||||
**
|
**
|
||||||
** ^A virtual table implementation is always free to return rows in any order
|
** ^A virtual table implementation is always free to return rows in any order
|
||||||
** it wants, as long as the "orderByConsumed" flag is not set. ^When the
|
** it wants, as long as the "orderByConsumed" flag is not set. ^When the
|
||||||
** the "orderByConsumed" flag is unset, the query planner will add extra
|
** "orderByConsumed" flag is unset, the query planner will add extra
|
||||||
** [bytecode] to ensure that the final results returned by the SQL query are
|
** [bytecode] to ensure that the final results returned by the SQL query are
|
||||||
** ordered correctly. The use of the "orderByConsumed" flag and the
|
** ordered correctly. The use of the "orderByConsumed" flag and the
|
||||||
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
||||||
@@ -10313,7 +10313,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
|
|||||||
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
|
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
|
||||||
** xFilter method which invokes these routines, and specifically
|
** xFilter method which invokes these routines, and specifically
|
||||||
** a parameter that was previously selected for all-at-once IN constraint
|
** a parameter that was previously selected for all-at-once IN constraint
|
||||||
** processing use the [sqlite3_vtab_in()] interface in the
|
** processing using the [sqlite3_vtab_in()] interface in the
|
||||||
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
|
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
|
||||||
** an xFilter argument that was selected for all-at-once IN constraint
|
** an xFilter argument that was selected for all-at-once IN constraint
|
||||||
** processing, then these routines return [SQLITE_ERROR].)^
|
** processing, then these routines return [SQLITE_ERROR].)^
|
||||||
@@ -10368,7 +10368,7 @@ SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut);
|
|||||||
** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V)
|
** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V)
|
||||||
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
||||||
** constraint is not available. ^The sqlite3_vtab_rhs_value() interface
|
** constraint is not available. ^The sqlite3_vtab_rhs_value() interface
|
||||||
** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
||||||
** something goes wrong.
|
** something goes wrong.
|
||||||
**
|
**
|
||||||
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
||||||
@@ -10396,8 +10396,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** KEYWORDS: {conflict resolution mode}
|
** KEYWORDS: {conflict resolution mode}
|
||||||
**
|
**
|
||||||
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
||||||
** inform a [virtual table] implementation what the [ON CONFLICT] mode
|
** inform a [virtual table] implementation of the [ON CONFLICT] mode
|
||||||
** is for the SQL statement being evaluated.
|
** for the SQL statement being evaluated.
|
||||||
**
|
**
|
||||||
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
||||||
** return value from the [sqlite3_set_authorizer()] callback and that
|
** return value from the [sqlite3_set_authorizer()] callback and that
|
||||||
@@ -10437,39 +10437,39 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
||||||
** <dd>^The "double" variable pointed to by the V parameter will be set to the
|
** <dd>^The "double" variable pointed to by the V parameter will be set to the
|
||||||
** query planner's estimate for the average number of rows output from each
|
** query planner's estimate for the average number of rows output from each
|
||||||
** iteration of the X-th loop. If the query planner's estimates was accurate,
|
** iteration of the X-th loop. If the query planner's estimate was accurate,
|
||||||
** then this value will approximate the quotient NVISIT/NLOOP and the
|
** then this value will approximate the quotient NVISIT/NLOOP and the
|
||||||
** product of this value for all prior loops with the same SELECTID will
|
** product of this value for all prior loops with the same SELECTID will
|
||||||
** be the NLOOP value for the current loop.
|
** be the NLOOP value for the current loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
|
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
|
||||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||||
** to a zero-terminated UTF-8 string containing the name of the index or table
|
** to a zero-terminated UTF-8 string containing the name of the index or table
|
||||||
** used for the X-th loop.
|
** used for the X-th loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
||||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||||
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
|
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
|
||||||
** description for the X-th loop.
|
** description for the X-th loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
||||||
** <dd>^The "int" variable pointed to by the V parameter will be set to the
|
** <dd>^The "int" variable pointed to by the V parameter will be set to the
|
||||||
** id for the X-th query plan element. The id value is unique within the
|
** id for the X-th query plan element. The id value is unique within the
|
||||||
** statement. The select-id is the same value as is output in the first
|
** statement. The select-id is the same value as is output in the first
|
||||||
** column of an [EXPLAIN QUERY PLAN] query.
|
** column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
|
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
|
||||||
** <dd>The "int" variable pointed to by the V parameter will be set to the
|
** <dd>The "int" variable pointed to by the V parameter will be set to the
|
||||||
** the id of the parent of the current query element, if applicable, or
|
** id of the parent of the current query element, if applicable, or
|
||||||
** to zero if the query element has no parent. This is the same value as
|
** to zero if the query element has no parent. This is the same value as
|
||||||
** returned in the second column of an [EXPLAIN QUERY PLAN] query.
|
** returned in the second column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
|
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
|
||||||
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
||||||
** according to the processor time-stamp counter, that elapsed while the
|
** according to the processor time-stamp counter, that elapsed while the
|
||||||
** query element was being processed. This value is not available for
|
** query element was being processed. This value is not available for
|
||||||
** all query elements - if it is unavailable the output variable is
|
** all query elements - if it is unavailable the output variable is
|
||||||
** set to -1.
|
** set to -1.</dd>
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_SCANSTAT_NLOOP 0
|
#define SQLITE_SCANSTAT_NLOOP 0
|
||||||
@@ -10510,8 +10510,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
||||||
**
|
**
|
||||||
** Parameter "idx" identifies the specific query element to retrieve statistics
|
** Parameter "idx" identifies the specific query element to retrieve statistics
|
||||||
** for. Query elements are numbered starting from zero. A value of -1 may be
|
** for. Query elements are numbered starting from zero. A value of -1 may
|
||||||
** to query for statistics regarding the entire query. ^If idx is out of range
|
** retrieve statistics for the entire query. ^If idx is out of range
|
||||||
** - less than -1 or greater than or equal to the total number of query
|
** - less than -1 or greater than or equal to the total number of query
|
||||||
** elements used to implement the statement - a non-zero value is returned and
|
** elements used to implement the statement - a non-zero value is returned and
|
||||||
** the variable that pOut points to is unchanged.
|
** the variable that pOut points to is unchanged.
|
||||||
@@ -10668,8 +10668,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
|
|||||||
** triggers; and so forth.
|
** triggers; and so forth.
|
||||||
**
|
**
|
||||||
** When the [sqlite3_blob_write()] API is used to update a blob column,
|
** When the [sqlite3_blob_write()] API is used to update a blob column,
|
||||||
** the pre-update hook is invoked with SQLITE_DELETE. This is because the
|
** the pre-update hook is invoked with SQLITE_DELETE, because
|
||||||
** in this case the new values are not available. In this case, when a
|
** the new values are not yet available. In this case, when a
|
||||||
** callback made with op==SQLITE_DELETE is actually a write using the
|
** callback made with op==SQLITE_DELETE is actually a write using the
|
||||||
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
||||||
** the index of the column being written. In other cases, where the
|
** the index of the column being written. In other cases, where the
|
||||||
@@ -10922,7 +10922,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||||||
** For an ordinary on-disk database file, the serialization is just a
|
** For an ordinary on-disk database file, the serialization is just a
|
||||||
** copy of the disk file. For an in-memory database or a "TEMP" database,
|
** copy of the disk file. For an in-memory database or a "TEMP" database,
|
||||||
** the serialization is the same sequence of bytes which would be written
|
** the serialization is the same sequence of bytes which would be written
|
||||||
** to disk if that database where backed up to disk.
|
** to disk if that database were backed up to disk.
|
||||||
**
|
**
|
||||||
** The usual case is that sqlite3_serialize() copies the serialization of
|
** The usual case is that sqlite3_serialize() copies the serialization of
|
||||||
** the database into memory obtained from [sqlite3_malloc64()] and returns
|
** the database into memory obtained from [sqlite3_malloc64()] and returns
|
||||||
@@ -10931,7 +10931,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||||||
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
||||||
** are made, and the sqlite3_serialize() function will return a pointer
|
** are made, and the sqlite3_serialize() function will return a pointer
|
||||||
** to the contiguous memory representation of the database that SQLite
|
** to the contiguous memory representation of the database that SQLite
|
||||||
** is currently using for that database, or NULL if the no such contiguous
|
** is currently using for that database, or NULL if no such contiguous
|
||||||
** memory representation of the database exists. A contiguous memory
|
** memory representation of the database exists. A contiguous memory
|
||||||
** representation of the database will usually only exist if there has
|
** representation of the database will usually only exist if there has
|
||||||
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
||||||
@@ -11002,7 +11002,7 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
|||||||
** database is currently in a read transaction or is involved in a backup
|
** database is currently in a read transaction or is involved in a backup
|
||||||
** operation.
|
** operation.
|
||||||
**
|
**
|
||||||
** It is not possible to deserialized into the TEMP database. If the
|
** It is not possible to deserialize into the TEMP database. If the
|
||||||
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
|
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
|
||||||
** function returns SQLITE_ERROR.
|
** function returns SQLITE_ERROR.
|
||||||
**
|
**
|
||||||
@@ -11024,7 +11024,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||||||
sqlite3 *db, /* The database connection */
|
sqlite3 *db, /* The database connection */
|
||||||
const char *zSchema, /* Which DB to reopen with the deserialization */
|
const char *zSchema, /* Which DB to reopen with the deserialization */
|
||||||
unsigned char *pData, /* The serialized database content */
|
unsigned char *pData, /* The serialized database content */
|
||||||
sqlite3_int64 szDb, /* Number bytes in the deserialization */
|
sqlite3_int64 szDb, /* Number of bytes in the deserialization */
|
||||||
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
|
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
|
||||||
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
||||||
);
|
);
|
||||||
@@ -11032,7 +11032,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||||||
/*
|
/*
|
||||||
** CAPI3REF: Flags for sqlite3_deserialize()
|
** CAPI3REF: Flags for sqlite3_deserialize()
|
||||||
**
|
**
|
||||||
** The following are allowed values for 6th argument (the F argument) to
|
** The following are allowed values for the 6th argument (the F argument) to
|
||||||
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
|
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
|
||||||
**
|
**
|
||||||
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
||||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750622754,
|
"lastModified": 1756217674,
|
||||||
"narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=",
|
"narHash": "sha256-TH1SfSP523QI7kcPiNtMAEuwZR3Jdz0MCDXPs7TS8uo=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1",
|
"rev": "4e7667a90c167f7a81d906e5a75cba4ad8bee620",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
14
metadata/en-US/changelogs/40.txt
Normal file
14
metadata/en-US/changelogs/40.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
* Added an option to stay connected to a handful of peers.
|
||||||
|
* Load more messages at a time.
|
||||||
|
* Fix a set of Android not responding errors.
|
||||||
|
* Target Android 15 (API level 35) to meet new requirements.
|
||||||
|
* Support JS-less webapps.
|
||||||
|
* Fix unnecessary tunnel disconnects.
|
||||||
|
* Many small user interface tweaks.
|
||||||
|
* Update:
|
||||||
|
* CodeMirror
|
||||||
|
* OpenSSL 3.5.1
|
||||||
|
* lit 3.3.1
|
||||||
|
* picohttpparser
|
||||||
|
* speedscope 1.23.0
|
||||||
|
* sqlite 3.50.4
|
9
metadata/en-US/changelogs/42.txt
Normal file
9
metadata/en-US/changelogs/42.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
* Private messages interface overhaul in progress.
|
||||||
|
* Added a loading indicator.
|
||||||
|
* Documented the core JavaScript.
|
||||||
|
* Fixed @-completion.
|
||||||
|
* Covered up launch on Android with the splash screen.
|
||||||
|
* Update:
|
||||||
|
* CodeMirror
|
||||||
|
* OpenSSL 3.5.2
|
||||||
|
* speedscope 1.23.1
|
1
package-lock.json
generated
1
package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.unprompted.tildefriends"
|
package="com.unprompted.tildefriends"
|
||||||
android:versionCode="39"
|
android:versionCode="43"
|
||||||
android:versionName="0.0.32.1">
|
android:versionName="0.2025.9-wip">
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<application
|
<application
|
||||||
|
@@ -19,12 +19,12 @@ import android.os.RemoteException;
|
|||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
import android.webkit.CookieManager;
|
import android.webkit.CookieManager;
|
||||||
import android.webkit.DownloadListener;
|
import android.webkit.DownloadListener;
|
||||||
import android.webkit.JsPromptResult;
|
import android.webkit.JsPromptResult;
|
||||||
@@ -43,6 +43,7 @@ import java.io.File;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class TildeFriendsActivity extends Activity {
|
public class TildeFriendsActivity extends Activity {
|
||||||
@@ -50,39 +51,44 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
TildeFriendsWebView web_view;
|
TildeFriendsWebView web_view;
|
||||||
String base_url;
|
String base_url;
|
||||||
String port_file_path;
|
String port_file_path;
|
||||||
|
Thread create_thread;
|
||||||
Thread server_thread;
|
Thread server_thread;
|
||||||
|
Thread log_thread;
|
||||||
ServiceConnection service_connection;
|
ServiceConnection service_connection;
|
||||||
FileObserver observer;
|
FileObserver observer;
|
||||||
|
LinkedBlockingQueue<String> log_queue = new LinkedBlockingQueue<String>();
|
||||||
|
|
||||||
private ValueCallback<Uri[]> upload_message;
|
private ValueCallback<Uri[]> upload_message;
|
||||||
private final static int FILECHOOSER_RESULT = 1;
|
private final static int FILECHOOSER_RESULT = 1;
|
||||||
private float touch_down_y;
|
private float touch_down_y;
|
||||||
|
private boolean ready = false;
|
||||||
|
private boolean loaded = false;
|
||||||
|
private boolean shutting_down = false;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Log.w("tildefriends", "Calling system.loadLibrary().");
|
log("Calling system.loadLibrary().");
|
||||||
System.loadLibrary("tildefriends");
|
System.loadLibrary("tildefriends");
|
||||||
Log.w("tildefriends", "system.loadLibrary() completed.");
|
log("system.loadLibrary() completed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path, ConnectivityManager connectivity_manager);
|
public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path, ConnectivityManager connectivity_manager);
|
||||||
public static native int tf_sandbox_main(int pipe_fd);
|
public static native int tf_sandbox_main(int pipe_fd);
|
||||||
|
|
||||||
@Override
|
public static void log(String message) {
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
if (s_activity != null && s_activity.log_queue != null && message != null) {
|
||||||
s_activity = this;
|
try {
|
||||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
s_activity.log_queue.put(message);
|
||||||
.detectLeakedClosableObjects()
|
} catch (InterruptedException e) {
|
||||||
.penaltyLog()
|
android.util.Log.w("tildefriends", message);
|
||||||
.build());
|
}
|
||||||
super.onCreate(savedInstanceState);
|
}
|
||||||
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
}
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
|
|
||||||
|
private void createThread() {
|
||||||
web_view = (TildeFriendsWebView)findViewById(R.id.web);
|
web_view = (TildeFriendsWebView)findViewById(R.id.web);
|
||||||
set_status("Extracting executable...");
|
log(String.format("getFilesDir() is %s", getFilesDir().toString()));
|
||||||
Log.w("tildefriends", String.format("getFilesDir() is %s", getFilesDir().toString()));
|
log(String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
|
||||||
Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
|
log(String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir));
|
||||||
Log.w("tildefriends", String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir));
|
|
||||||
|
|
||||||
port_file_path = getFilesDir().toString() + "/port.txt";
|
port_file_path = getFilesDir().toString() + "/port.txt";
|
||||||
new File(port_file_path).delete();
|
new File(port_file_path).delete();
|
||||||
@@ -90,178 +96,256 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
|
|
||||||
TildeFriendsActivity activity = this;
|
TildeFriendsActivity activity = this;
|
||||||
|
|
||||||
Log.w("tildefriends", "Watching for changes in: " + getFilesDir().toString());
|
|
||||||
observer = make_file_observer(getFilesDir().toString(), port_file_path);
|
|
||||||
observer.startWatching();
|
|
||||||
|
|
||||||
set_status("Starting server...");
|
|
||||||
server_thread = new Thread(new Runnable() {
|
server_thread = new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Log.w("tildefriends", "Calling tf_server_main.");
|
log("Watching for changes in: " + getFilesDir().toString());
|
||||||
|
observer = make_file_observer(getFilesDir().toString(), port_file_path);
|
||||||
|
observer.startWatching();
|
||||||
|
|
||||||
|
log("Calling tf_server_main.");
|
||||||
int result = tf_server_main(
|
int result = tf_server_main(
|
||||||
getFilesDir().toString(),
|
getFilesDir().toString(),
|
||||||
getPackageResourcePath().toString(),
|
getPackageResourcePath().toString(),
|
||||||
port_file_path,
|
port_file_path,
|
||||||
(ConnectivityManager)getApplicationContext().getSystemService(CONNECTIVITY_SERVICE));
|
(ConnectivityManager)getApplicationContext().getSystemService(CONNECTIVITY_SERVICE));
|
||||||
Log.w("tildefriends", "tf_server_main returned " + result + ".");
|
log("tf_server_main returned " + result + ".");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
server_thread.start();
|
server_thread.start();
|
||||||
|
|
||||||
web_view.getSettings().setJavaScriptEnabled(true);
|
runOnUiThread(() -> {
|
||||||
web_view.getSettings().setDatabaseEnabled(true);
|
web_view.getSettings().setJavaScriptEnabled(true);
|
||||||
web_view.getSettings().setDomStorageEnabled(true);
|
web_view.getSettings().setDomStorageEnabled(true);
|
||||||
|
|
||||||
set_database_path();
|
set_database_enabled();
|
||||||
|
set_database_path();
|
||||||
|
|
||||||
web_view.setDownloadListener(new DownloadListener() {
|
web_view.setDownloadListener(new DownloadListener() {
|
||||||
public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) {
|
public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) {
|
||||||
Log.w("tildefriends", "Let's download: " + url + " (" + content_disposition + ")");
|
log("Let's download: " + url + " (" + content_disposition + ")");
|
||||||
String file_name = URLUtil.guessFileName(url, content_disposition, mime_type);
|
String file_name = URLUtil.guessFileName(url, content_disposition, mime_type);
|
||||||
if (url.startsWith("data:") && url.indexOf(',') != -1) {
|
if (url.startsWith("data:") && url.indexOf(',') != -1) {
|
||||||
String b64 = url.substring(url.indexOf(',') + 1);
|
String b64 = url.substring(url.indexOf(',') + 1);
|
||||||
byte[] data = Base64.decode(b64, Base64.DEFAULT);
|
byte[] data = Base64.decode(b64, Base64.DEFAULT);
|
||||||
Log.w("tildefriends", "Downloaded " + data.length + " bytes.");
|
log("Downloaded " + data.length + " bytes.");
|
||||||
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
try (OutputStream stream = new FileOutputStream(new File(path, file_name))) {
|
try (OutputStream stream = new FileOutputStream(new File(path, file_name))) {
|
||||||
stream.write(data);
|
stream.write(data);
|
||||||
} catch (java.io.IOException e) {
|
} catch (java.io.IOException e) {
|
||||||
Log.w("tildefriends", "IOException: " + e.toString());
|
log("IOException: " + e.toString());
|
||||||
|
}
|
||||||
|
Toast.makeText(getApplicationContext(), "Downloaded File", Toast.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
|
||||||
|
request.setMimeType(mime_type);
|
||||||
|
String cookies = CookieManager.getInstance().getCookie(url);
|
||||||
|
request.addRequestHeader("cookie", cookies);
|
||||||
|
request.addRequestHeader("User-Agent", userAgent);
|
||||||
|
request.setDescription("Downloading file...");
|
||||||
|
request.setTitle(file_name);
|
||||||
|
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||||
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, content_disposition, mime_type));
|
||||||
|
DownloadManager dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
|
||||||
|
dm.enqueue(request);
|
||||||
|
Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
Toast.makeText(getApplicationContext(), "Downloaded File", Toast.LENGTH_LONG).show();
|
|
||||||
} else {
|
|
||||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
|
|
||||||
request.setMimeType(mime_type);
|
|
||||||
String cookies = CookieManager.getInstance().getCookie(url);
|
|
||||||
request.addRequestHeader("cookie", cookies);
|
|
||||||
request.addRequestHeader("User-Agent", userAgent);
|
|
||||||
request.setDescription("Downloading file...");
|
|
||||||
request.setTitle(file_name);
|
|
||||||
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
|
||||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, content_disposition, mime_type));
|
|
||||||
DownloadManager dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
|
|
||||||
dm.enqueue(request);
|
|
||||||
Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
web_view.setWebChromeClient(new WebChromeClient() {
|
web_view.setWebChromeClient(new WebChromeClient() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
|
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
|
||||||
new AlertDialog.Builder(view.getContext())
|
new AlertDialog.Builder(view.getContext())
|
||||||
.setTitle("Tilde Friends")
|
.setTitle("Tilde Friends")
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||||
{
|
|
||||||
public void onClick(DialogInterface dialog, int which)
|
|
||||||
{
|
{
|
||||||
result.confirm();
|
public void onClick(DialogInterface dialog, int which)
|
||||||
}
|
{
|
||||||
})
|
result.confirm();
|
||||||
.create()
|
}
|
||||||
.show();
|
})
|
||||||
return true;
|
.create()
|
||||||
}
|
.show();
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
|
|
||||||
new AlertDialog.Builder(view.getContext())
|
|
||||||
.setTitle("Tilde Friends")
|
|
||||||
.setMessage(message)
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
|
||||||
{
|
|
||||||
public void onClick(DialogInterface dialog, int which)
|
|
||||||
{
|
|
||||||
result.confirm();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
|
||||||
{
|
|
||||||
public void onClick(DialogInterface dialog, int which)
|
|
||||||
{
|
|
||||||
result.cancel();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
|
|
||||||
EditText input = new EditText(view.getContext());
|
|
||||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
|
||||||
input.setText(defaultValue);
|
|
||||||
|
|
||||||
new AlertDialog.Builder(view.getContext())
|
|
||||||
.setTitle("Tilde Friends")
|
|
||||||
.setMessage(message)
|
|
||||||
.setView(input)
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
|
||||||
{
|
|
||||||
public void onClick(DialogInterface dialog, int which)
|
|
||||||
{
|
|
||||||
result.confirm(input.getText().toString());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
|
||||||
{
|
|
||||||
public void onClick(DialogInterface dialog, int which)
|
|
||||||
{
|
|
||||||
result.cancel();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
** https://stackoverflow.com/questions/5907369/file-upload-in-webview
|
|
||||||
** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> message, WebChromeClient.FileChooserParams params) {
|
|
||||||
upload_message = message;
|
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
||||||
intent.setType("*/*");
|
|
||||||
TildeFriendsActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), TildeFriendsActivity.FILECHOOSER_RESULT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
|
|
||||||
Log.d("tildefriends", consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
web_view.setWebViewClient(new WebViewClient() {
|
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
|
|
||||||
{
|
|
||||||
if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, request.getUrl()));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
|
||||||
|
new AlertDialog.Builder(view.getContext())
|
||||||
|
.setTitle("Tilde Friends")
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||||
|
{
|
||||||
|
public void onClick(DialogInterface dialog, int which)
|
||||||
|
{
|
||||||
|
result.confirm();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
||||||
|
{
|
||||||
|
public void onClick(DialogInterface dialog, int which)
|
||||||
|
{
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
|
||||||
|
EditText input = new EditText(view.getContext());
|
||||||
|
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
|
input.setText(defaultValue);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(view.getContext())
|
||||||
|
.setTitle("Tilde Friends")
|
||||||
|
.setMessage(message)
|
||||||
|
.setView(input)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||||
|
{
|
||||||
|
public void onClick(DialogInterface dialog, int which)
|
||||||
|
{
|
||||||
|
result.confirm(input.getText().toString());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
||||||
|
{
|
||||||
|
public void onClick(DialogInterface dialog, int which)
|
||||||
|
{
|
||||||
|
result.cancel();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** https://stackoverflow.com/questions/5907369/file-upload-in-webview
|
||||||
|
** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> message, WebChromeClient.FileChooserParams params) {
|
||||||
|
upload_message = message;
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("*/*");
|
||||||
|
TildeFriendsActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), TildeFriendsActivity.FILECHOOSER_RESULT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
|
||||||
|
log(consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
web_view.setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
|
||||||
|
{
|
||||||
|
if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, request.getUrl()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
s_activity.loaded = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
s_activity.create_thread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
s_activity = this;
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
log_thread = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (!s_activity.shutting_down) {
|
||||||
|
try {
|
||||||
|
String message = log_queue.take();
|
||||||
|
if (message != null) {
|
||||||
|
android.util.Log.w("tildefriends", message);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log_thread.start();
|
||||||
|
StrictMode.setThreadPolicy(
|
||||||
|
new StrictMode.ThreadPolicy.Builder()
|
||||||
|
.detectAll()
|
||||||
|
.penaltyDialog()
|
||||||
|
.penaltyLog()
|
||||||
|
.build());
|
||||||
|
StrictMode.setVmPolicy(
|
||||||
|
new StrictMode.VmPolicy.Builder()
|
||||||
|
.detectLeakedClosableObjects()
|
||||||
|
.detectAll()
|
||||||
|
.penaltyLog()
|
||||||
|
.build());
|
||||||
|
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
TextView refresh = (TextView)findViewById(R.id.refresh);
|
TextView refresh = (TextView)findViewById(R.id.refresh);
|
||||||
refresh.setVisibility(View.GONE);
|
refresh.setVisibility(View.GONE);
|
||||||
refresh.setText("REFRESH");
|
refresh.setText("REFRESH");
|
||||||
|
|
||||||
|
final View content = findViewById(android.R.id.content);
|
||||||
|
content.getViewTreeObserver().addOnPreDrawListener(
|
||||||
|
new ViewTreeObserver.OnPreDrawListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw() {
|
||||||
|
if (s_activity.ready && s_activity.loaded) {
|
||||||
|
content.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
create_thread = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
s_activity.createThread();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
create_thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy()
|
protected void onDestroy()
|
||||||
{
|
{
|
||||||
super.onDestroy();
|
try {
|
||||||
|
shutting_down = true;
|
||||||
|
if (log_queue != null) {
|
||||||
|
log_queue.put("Goodbye.");
|
||||||
|
}
|
||||||
|
log_thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
log_thread = null;
|
||||||
s_activity = null;
|
s_activity = null;
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -353,46 +437,33 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_status(String text) {
|
|
||||||
TextView text_view = (TextView)findViewById(R.id.text);
|
|
||||||
web_view.setVisibility(View.GONE);
|
|
||||||
text_view.setVisibility(View.VISIBLE);
|
|
||||||
text_view.setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hide_status() {
|
|
||||||
TextView text_view = (TextView)findViewById(R.id.text);
|
|
||||||
web_view.setVisibility(View.VISIBLE);
|
|
||||||
text_view.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void start_sandbox(int pipe_fd) {
|
public static void start_sandbox(int pipe_fd) {
|
||||||
Log.w("tildefriends", "starting service with fd: " + pipe_fd);
|
log("starting service with fd: " + pipe_fd);
|
||||||
Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class);
|
Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class);
|
||||||
s_activity.service_connection = new ServiceConnection() {
|
s_activity.service_connection = new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
public void onBindingDied(ComponentName name) {
|
public void onBindingDied(ComponentName name) {
|
||||||
Log.w("tildefriends", "onBindingDied");
|
log("onBindingDied");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNullBinding(ComponentName name) {
|
public void onNullBinding(ComponentName name) {
|
||||||
Log.w("tildefriends", "onNullBinding");
|
log("onNullBinding");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||||
Log.w("tildefriends", "onServiceConnected");
|
log("onServiceConnected");
|
||||||
Parcel data = Parcel.obtain();
|
Parcel data = Parcel.obtain();
|
||||||
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(pipe_fd)) {
|
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(pipe_fd)) {
|
||||||
data.writeParcelable(pfd, 0);
|
data.writeParcelable(pfd, 0);
|
||||||
} catch (java.io.IOException e) {
|
} catch (java.io.IOException e) {
|
||||||
Log.w("tildefriends", "IOException: " + e);
|
log("IOException: " + e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
binder.transact(TildeFriendsSandboxService.START_CALL, data, null, IBinder.FLAG_ONEWAY);
|
binder.transact(TildeFriendsSandboxService.START_CALL, data, null, IBinder.FLAG_ONEWAY);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.w("tildefriends", "RemoteException");
|
log("RemoteException");
|
||||||
} finally {
|
} finally {
|
||||||
data.recycle();
|
data.recycle();
|
||||||
}
|
}
|
||||||
@@ -400,14 +471,14 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
Log.w("tildefriends", "onServiceDisconnected");
|
log("onServiceDisconnected");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE);
|
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void stop_sandbox() {
|
public static void stop_sandbox() {
|
||||||
Log.w("tildefriends", "stop_sandbox");
|
log("stop_sandbox");
|
||||||
if (s_activity.service_connection != null) {
|
if (s_activity.service_connection != null) {
|
||||||
s_activity.unbindService(s_activity.service_connection);
|
s_activity.unbindService(s_activity.service_connection);
|
||||||
s_activity.service_connection = null;
|
s_activity.service_connection = null;
|
||||||
@@ -419,14 +490,11 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
if (port >= 0) {
|
if (port >= 0) {
|
||||||
base_url = "http://127.0.0.1:" + String.valueOf(port) + "/";
|
base_url = "http://127.0.0.1:" + String.valueOf(port) + "/";
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
hide_status();
|
ready = true;
|
||||||
web_view.loadUrl(base_url + "login/auto");
|
web_view.loadUrl(base_url + "login/auto");
|
||||||
});
|
});
|
||||||
|
observer.stopWatching();
|
||||||
observer = null;
|
observer = null;
|
||||||
} else {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
set_status("Waiting to connect...");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,4 +519,10 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
web_view.getSettings().setDatabasePath(getDatabasePath("webview").getPath());
|
web_view.getSettings().setDatabasePath(getDatabasePath("webview").getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void set_database_enabled()
|
||||||
|
{
|
||||||
|
web_view.getSettings().setDatabaseEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,6 @@ import android.os.Binder;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class TildeFriendsSandboxService extends Service {
|
public class TildeFriendsSandboxService extends Service {
|
||||||
public static final int START_CALL = IBinder.FIRST_CALL_TRANSACTION;
|
public static final int START_CALL = IBinder.FIRST_CALL_TRANSACTION;
|
||||||
@@ -14,12 +13,12 @@ public class TildeFriendsSandboxService extends Service {
|
|||||||
Thread thread;
|
Thread thread;
|
||||||
|
|
||||||
public int onStartCommand(Intent intent, int flags, int start_id) {
|
public int onStartCommand(Intent intent, int flags, int start_id) {
|
||||||
Log.w("tildefriends", "TildeFriendsSandboxService: onStartCommand");
|
TildeFriendsActivity.log("TildeFriendsSandboxService: onStartCommand");
|
||||||
return super.onStartCommand(intent, flags, start_id);
|
return super.onStartCommand(intent, flags, start_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
Log.w("tildefriends", "TildeFriendsSandboxService: onDestroy");
|
TildeFriendsActivity.log("TildeFriendsSandboxService: onDestroy");
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,9 +26,9 @@ public class TildeFriendsSandboxService extends Service {
|
|||||||
thread = new Thread(new Runnable() {
|
thread = new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Log.w("tildefriends", "Calling tf_sandbox_main.");
|
TildeFriendsActivity.log("Calling tf_sandbox_main.");
|
||||||
int result = TildeFriendsActivity.tf_sandbox_main(pipe_fd);
|
int result = TildeFriendsActivity.tf_sandbox_main(pipe_fd);
|
||||||
Log.w("tildefriends", "tf_sandbox_main returned " + result + ".");
|
TildeFriendsActivity.log("tf_sandbox_main returned " + result + ".");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
thread.start();
|
thread.start();
|
||||||
@@ -43,7 +42,7 @@ public class TildeFriendsSandboxService extends Service {
|
|||||||
if (code == START_CALL) {
|
if (code == START_CALL) {
|
||||||
ParcelFileDescriptor pfd = read_pfd(data);
|
ParcelFileDescriptor pfd = read_pfd(data);
|
||||||
if (pfd != null) {
|
if (pfd != null) {
|
||||||
Log.w("tildefriends", "fd is " + pfd.getFd());
|
TildeFriendsActivity.log("fd is " + pfd.getFd());
|
||||||
start_thread(pfd.detachFd());
|
start_thread(pfd.detachFd());
|
||||||
try {
|
try {
|
||||||
pfd.close();
|
pfd.close();
|
||||||
|
@@ -3,16 +3,13 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:background="#000">
|
||||||
<com.unprompted.tildefriends.TildeFriendsWebView
|
<com.unprompted.tildefriends.TildeFriendsWebView
|
||||||
android:id="@+id/web"
|
android:id="@+id/web"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent"/>
|
||||||
<TextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_horizontal|center_vertical"/>
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/refresh"
|
android:id="@+id/refresh"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
214
src/api.js.c
214
src/api.js.c
@@ -1,11 +1,225 @@
|
|||||||
#include "api.js.h"
|
#include "api.js.h"
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
#include <quickjs.h>
|
#include <quickjs.h>
|
||||||
|
|
||||||
|
typedef struct _app_path_pair_t
|
||||||
|
{
|
||||||
|
const char* app;
|
||||||
|
const char* path;
|
||||||
|
} app_path_pair_t;
|
||||||
|
|
||||||
|
typedef struct _get_apps_t
|
||||||
|
{
|
||||||
|
app_path_pair_t* apps;
|
||||||
|
int count;
|
||||||
|
JSContext* context;
|
||||||
|
JSValue promise[2];
|
||||||
|
char user[];
|
||||||
|
} get_apps_t;
|
||||||
|
|
||||||
|
static void _tf_api_core_apps_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
get_apps_t* work = user_data;
|
||||||
|
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
const char* apps = tf_ssb_db_get_property(ssb, work->user, "apps");
|
||||||
|
if (apps)
|
||||||
|
{
|
||||||
|
JSValue apps_array = JS_ParseJSON(context, apps, strlen(apps), NULL);
|
||||||
|
if (JS_IsArray(context, apps_array))
|
||||||
|
{
|
||||||
|
int length = tf_util_get_length(context, apps_array);
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
JSValue name = JS_GetPropertyUint32(context, apps_array, i);
|
||||||
|
const char* name_string = JS_ToCString(context, name);
|
||||||
|
if (name_string)
|
||||||
|
{
|
||||||
|
work->apps = tf_resize_vec(work->apps, sizeof(app_path_pair_t) * (work->count + 1));
|
||||||
|
work->apps[work->count].app = tf_strdup(name_string);
|
||||||
|
size_t size = strlen("path:") + strlen(name_string) + 1;
|
||||||
|
char* path_key = tf_malloc(size);
|
||||||
|
snprintf(path_key, size, "path:%s", name_string);
|
||||||
|
work->apps[work->count].path = tf_ssb_db_get_property(ssb, work->user, path_key);
|
||||||
|
tf_free(path_key);
|
||||||
|
work->count++;
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, name_string);
|
||||||
|
JS_FreeValue(context, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, apps_array);
|
||||||
|
}
|
||||||
|
tf_free((void*)apps);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_api_core_apps_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
get_apps_t* work = user_data;
|
||||||
|
JSContext* context = work->context;
|
||||||
|
JSValue result = JS_NewObject(context);
|
||||||
|
for (int i = 0; i < work->count; i++)
|
||||||
|
{
|
||||||
|
JS_SetPropertyStr(context, result, work->apps[i].app, JS_NewString(context, work->apps[i].path));
|
||||||
|
}
|
||||||
|
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
for (int i = 0; i < work->count; i++)
|
||||||
|
{
|
||||||
|
tf_free((void*)work->apps[i].app);
|
||||||
|
tf_free((void*)work->apps[i].path);
|
||||||
|
}
|
||||||
|
tf_free(work->apps);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_api_core_apps(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
JSValue user = argv[0];
|
||||||
|
JSValue process = data[0];
|
||||||
|
const char* user_string = JS_IsString(user) ? JS_ToCString(context, user) : NULL;
|
||||||
|
|
||||||
|
if (JS_IsObject(process))
|
||||||
|
{
|
||||||
|
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
||||||
|
if (JS_IsObject(credentials))
|
||||||
|
{
|
||||||
|
JSValue session = JS_GetPropertyStr(context, credentials, "session");
|
||||||
|
if (JS_IsObject(session))
|
||||||
|
{
|
||||||
|
JSValue session_name = JS_GetPropertyStr(context, session, "name");
|
||||||
|
const char* session_name_string = JS_IsString(session_name) ? JS_ToCString(context, session_name) : NULL;
|
||||||
|
if (user_string && session_name_string && strcmp(user_string, session_name_string) && strcmp(user_string, "core"))
|
||||||
|
{
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
user_string = NULL;
|
||||||
|
}
|
||||||
|
else if (!user_string)
|
||||||
|
{
|
||||||
|
user_string = session_name_string;
|
||||||
|
session_name_string = NULL;
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, session_name_string);
|
||||||
|
JS_FreeValue(context, session_name);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, session);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user_string)
|
||||||
|
{
|
||||||
|
get_apps_t* work = tf_malloc(sizeof(get_apps_t) + strlen(user_string) + 1);
|
||||||
|
*work = (get_apps_t) {
|
||||||
|
.context = context,
|
||||||
|
};
|
||||||
|
memcpy(work->user, user_string, strlen(user_string) + 1);
|
||||||
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
|
||||||
|
tf_task_t* task = tf_task_get(context);
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
tf_ssb_run_work(ssb, _tf_api_core_apps_work, _tf_api_core_apps_after_work, work);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = JS_NewObject(context);
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_api_core_register(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
JSValue event_name = argv[0];
|
||||||
|
JSValue handler = argv[1];
|
||||||
|
JSValue process = data[0];
|
||||||
|
JSValue event_handlers = JS_GetPropertyStr(context, process, "eventHandlers");
|
||||||
|
JSAtom atom = JS_ValueToAtom(context, event_name);
|
||||||
|
JSValue array = JS_GetProperty(context, event_handlers, atom);
|
||||||
|
if (!JS_IsArray(context, array))
|
||||||
|
{
|
||||||
|
JS_FreeValue(context, array);
|
||||||
|
array = JS_NewArray(context);
|
||||||
|
JS_SetProperty(context, event_handlers, atom, JS_DupValue(context, array));
|
||||||
|
}
|
||||||
|
JS_SetPropertyUint32(context, array, tf_util_get_length(context, array), JS_DupValue(context, handler));
|
||||||
|
JS_FreeValue(context, array);
|
||||||
|
JS_FreeAtom(context, atom);
|
||||||
|
JS_FreeValue(context, event_handlers);
|
||||||
|
return JS_UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_api_core_unregister(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
JSValue event_name = argv[0];
|
||||||
|
JSValue handler = argv[1];
|
||||||
|
JSValue process = data[0];
|
||||||
|
JSValue event_handlers = JS_GetPropertyStr(context, process, "eventHandlers");
|
||||||
|
JSAtom atom = JS_ValueToAtom(context, event_name);
|
||||||
|
JSValue array = JS_GetProperty(context, event_handlers, atom);
|
||||||
|
|
||||||
|
if (JS_IsArray(context, array))
|
||||||
|
{
|
||||||
|
JSValue index_of = JS_GetPropertyStr(context, array, "indexOf");
|
||||||
|
|
||||||
|
JSValue index = JS_Call(context, index_of, array, 1, &handler);
|
||||||
|
int int_index = -1;
|
||||||
|
JS_ToInt32(context, &int_index, index);
|
||||||
|
if (int_index != -1)
|
||||||
|
{
|
||||||
|
JSValue splice = JS_GetPropertyStr(context, array, "splice");
|
||||||
|
JSValue splice_args[] =
|
||||||
|
{
|
||||||
|
index,
|
||||||
|
JS_NewInt32(context, 1),
|
||||||
|
};
|
||||||
|
JSValue result = JS_Call(context, splice, array, 2, splice_args);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
JS_FreeValue(context, splice);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, index);
|
||||||
|
JS_FreeValue(context, index_of);
|
||||||
|
|
||||||
|
if (tf_util_get_length(context, array) == 0)
|
||||||
|
{
|
||||||
|
JS_DeleteProperty(context, event_handlers, atom, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeValue(context, array);
|
||||||
|
JS_FreeAtom(context, atom);
|
||||||
|
JS_FreeValue(context, event_handlers);
|
||||||
|
return JS_UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
|
JSValue imports = argv[0];
|
||||||
|
JSValue process = argv[1];
|
||||||
|
JSValue core = JS_GetPropertyStr(context, imports, "core");
|
||||||
|
JS_SetPropertyStr(context, core, "apps", JS_NewCFunctionData(context, _tf_api_core_apps, 1, 0, 1, &process));
|
||||||
|
JS_SetPropertyStr(context, core, "register", JS_NewCFunctionData(context, _tf_api_core_register, 2, 0, 1, &process));
|
||||||
|
JS_SetPropertyStr(context, core, "unregister", JS_NewCFunctionData(context, _tf_api_core_unregister, 2, 0, 1, &process));
|
||||||
|
JS_FreeValue(context, core);
|
||||||
return JS_UNDEFINED;
|
return JS_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
252
src/httpd.app.c
Normal file
252
src/httpd.app.c
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include "picohttpparser.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _app_blob_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
bool found;
|
||||||
|
bool not_modified;
|
||||||
|
bool use_handler;
|
||||||
|
bool use_static;
|
||||||
|
void* data;
|
||||||
|
size_t size;
|
||||||
|
char app_blob_id[k_blob_id_len];
|
||||||
|
const char* file;
|
||||||
|
tf_httpd_user_app_t* user_app;
|
||||||
|
char etag[256];
|
||||||
|
} app_blob_t;
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
app_blob_t* data = user_data;
|
||||||
|
tf_http_request_t* request = data->request;
|
||||||
|
if (request->path[0] == '/' && request->path[1] == '~')
|
||||||
|
{
|
||||||
|
const char* last_slash = strchr(request->path + 1, '/');
|
||||||
|
if (last_slash)
|
||||||
|
{
|
||||||
|
last_slash = strchr(last_slash + 1, '/');
|
||||||
|
}
|
||||||
|
data->user_app = last_slash ? tf_httpd_parse_user_app_from_path(request->path, last_slash) : NULL;
|
||||||
|
if (data->user_app)
|
||||||
|
{
|
||||||
|
size_t path_length = strlen("path:") + strlen(data->user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(path_length);
|
||||||
|
snprintf(app_path, path_length, "path:%s", data->user_app->app);
|
||||||
|
const char* value = tf_ssb_db_get_property(ssb, data->user_app->user, app_path);
|
||||||
|
tf_string_set(data->app_blob_id, sizeof(data->app_blob_id), value);
|
||||||
|
tf_free(app_path);
|
||||||
|
tf_free((void*)value);
|
||||||
|
data->file = last_slash + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request->path[0] == '/' && request->path[1] == '&')
|
||||||
|
{
|
||||||
|
const char* end = strstr(request->path, ".sha256/");
|
||||||
|
if (end)
|
||||||
|
{
|
||||||
|
snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1);
|
||||||
|
data->file = end + strlen(".sha256/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* app_blob = NULL;
|
||||||
|
size_t app_blob_size = 0;
|
||||||
|
if (*data->app_blob_id && tf_ssb_db_blob_get(ssb, data->app_blob_id, (uint8_t**)&app_blob, &app_blob_size))
|
||||||
|
{
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue app_object = JS_ParseJSON(context, app_blob, app_blob_size, NULL);
|
||||||
|
JSValue files = JS_GetPropertyStr(context, app_object, "files");
|
||||||
|
JSValue blob_id = JS_GetPropertyStr(context, files, data->file);
|
||||||
|
if (JS_IsUndefined(blob_id))
|
||||||
|
{
|
||||||
|
blob_id = JS_GetPropertyStr(context, files, "handler.js");
|
||||||
|
if (!JS_IsUndefined(blob_id))
|
||||||
|
{
|
||||||
|
data->use_handler = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* blob_id_str = JS_ToCString(context, blob_id);
|
||||||
|
if (blob_id_str)
|
||||||
|
{
|
||||||
|
snprintf(data->etag, sizeof(data->etag), "\"%s\"", blob_id_str);
|
||||||
|
const char* match = tf_http_request_get_header(data->request, "if-none-match");
|
||||||
|
if (match && strcmp(match, data->etag) == 0)
|
||||||
|
{
|
||||||
|
data->not_modified = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data->found = tf_ssb_db_blob_get(ssb, blob_id_str, (uint8_t**)&data->data, &data->size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, blob_id_str);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, blob_id);
|
||||||
|
JS_FreeValue(context, files);
|
||||||
|
JS_FreeValue(context, app_object);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
tf_free(app_blob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_call_app_handler(tf_ssb_t* ssb, tf_http_request_t* request, const char* app_blob_id, const char* path, const char* package_owner, const char* app)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue global = JS_GetGlobalObject(context);
|
||||||
|
JSValue exports = JS_GetPropertyStr(context, global, "exports");
|
||||||
|
JSValue call_app_handler = JS_GetPropertyStr(context, exports, "callAppHandler");
|
||||||
|
|
||||||
|
JSValue response = tf_httpd_make_response_object(context, request);
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
JSValue handler_blob_id = JS_NewString(context, app_blob_id);
|
||||||
|
JSValue path_value = JS_NewString(context, path);
|
||||||
|
JSValue package_owner_value = JS_NewString(context, package_owner);
|
||||||
|
JSValue app_value = JS_NewString(context, app);
|
||||||
|
JSValue query_value = request->query ? JS_NewString(context, request->query) : JS_UNDEFINED;
|
||||||
|
|
||||||
|
JSValue headers = JS_NewObject(context);
|
||||||
|
for (int i = 0; i < request->headers_count; i++)
|
||||||
|
{
|
||||||
|
char name[256] = "";
|
||||||
|
snprintf(name, sizeof(name), "%.*s", (int)request->headers[i].name_len, request->headers[i].name);
|
||||||
|
JS_SetPropertyStr(context, headers, name, JS_NewStringLen(context, request->headers[i].value, request->headers[i].value_len));
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue args[] = {
|
||||||
|
response,
|
||||||
|
handler_blob_id,
|
||||||
|
path_value,
|
||||||
|
query_value,
|
||||||
|
headers,
|
||||||
|
package_owner_value,
|
||||||
|
app_value,
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue result = JS_Call(context, call_app_handler, JS_NULL, tf_countof(args), args);
|
||||||
|
tf_util_report_error(context, result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
|
||||||
|
JS_FreeValue(context, headers);
|
||||||
|
JS_FreeValue(context, query_value);
|
||||||
|
JS_FreeValue(context, app_value);
|
||||||
|
JS_FreeValue(context, package_owner_value);
|
||||||
|
JS_FreeValue(context, handler_blob_id);
|
||||||
|
JS_FreeValue(context, path_value);
|
||||||
|
JS_FreeValue(context, response);
|
||||||
|
JS_FreeValue(context, call_app_handler);
|
||||||
|
JS_FreeValue(context, exports);
|
||||||
|
JS_FreeValue(context, global);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
app_blob_t* data = user_data;
|
||||||
|
if (data->not_modified)
|
||||||
|
{
|
||||||
|
tf_http_respond(data->request, 304, NULL, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
else if (data->use_static)
|
||||||
|
{
|
||||||
|
tf_httpd_endpoint_static(data->request);
|
||||||
|
}
|
||||||
|
else if (data->use_handler)
|
||||||
|
{
|
||||||
|
_httpd_call_app_handler(ssb, data->request, data->app_blob_id, data->file, data->user_app->user, data->user_app->app);
|
||||||
|
}
|
||||||
|
else if (data->found)
|
||||||
|
{
|
||||||
|
const char* mime_type = tf_httpd_ext_to_content_type(strrchr(data->request->path, '.'), false);
|
||||||
|
if (!mime_type)
|
||||||
|
{
|
||||||
|
mime_type = tf_httpd_magic_bytes_to_content_type(data->data, data->size);
|
||||||
|
}
|
||||||
|
const char* headers[] = {
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
"*",
|
||||||
|
"Content-Security-Policy",
|
||||||
|
"sandbox allow-downloads allow-top-navigation-by-user-activation",
|
||||||
|
"Content-Type",
|
||||||
|
mime_type ? mime_type : "application/binary",
|
||||||
|
"etag",
|
||||||
|
data->etag,
|
||||||
|
};
|
||||||
|
tf_http_respond(data->request, 200, headers, tf_countof(headers) / 2, data->data, data->size);
|
||||||
|
}
|
||||||
|
tf_free(data->user_app);
|
||||||
|
tf_free(data->data);
|
||||||
|
tf_http_request_unref(data->request);
|
||||||
|
tf_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_app(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
app_blob_t* data = tf_malloc(sizeof(app_blob_t));
|
||||||
|
*data = (app_blob_t) { .request = request };
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_app_blob_work, _httpd_endpoint_app_blob_after_work, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_app_socket(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue global = JS_GetGlobalObject(context);
|
||||||
|
JSValue exports = JS_GetPropertyStr(context, global, "exports");
|
||||||
|
JSValue app_socket = JS_GetPropertyStr(context, exports, "app_socket");
|
||||||
|
|
||||||
|
JSValue request_object = JS_NewObject(context);
|
||||||
|
JSValue headers = JS_NewObject(context);
|
||||||
|
for (int i = 0; i < request->headers_count; i++)
|
||||||
|
{
|
||||||
|
JS_SetPropertyStr(context, headers, request->headers[i].name, JS_NewString(context, request->headers[i].value));
|
||||||
|
}
|
||||||
|
JS_SetPropertyStr(context, request_object, "headers", headers);
|
||||||
|
|
||||||
|
JSValue response = tf_httpd_make_response_object(context, request);
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
|
||||||
|
JSValue args[] = {
|
||||||
|
request_object,
|
||||||
|
response,
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue result = JS_Call(context, app_socket, JS_NULL, tf_countof(args), args);
|
||||||
|
tf_util_report_error(context, result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
|
||||||
|
for (int i = 0; i < tf_countof(args); i++)
|
||||||
|
{
|
||||||
|
JS_FreeValue(context, args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeValue(context, app_socket);
|
||||||
|
JS_FreeValue(context, exports);
|
||||||
|
JS_FreeValue(context, global);
|
||||||
|
}
|
91
src/httpd.delete.c
Normal file
91
src/httpd.delete.c
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
typedef struct _delete_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
const char* session;
|
||||||
|
int response;
|
||||||
|
} delete_t;
|
||||||
|
|
||||||
|
static void _httpd_endpoint_delete_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
delete_t* delete = user_data;
|
||||||
|
tf_http_request_t* request = delete->request;
|
||||||
|
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, delete->session);
|
||||||
|
JSValue user = JS_GetPropertyStr(context, jwt, "name");
|
||||||
|
const char* user_string = JS_ToCString(context, user);
|
||||||
|
if (user_string && tf_httpd_is_name_valid(user_string))
|
||||||
|
{
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/delete");
|
||||||
|
if (user_app)
|
||||||
|
{
|
||||||
|
if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, NULL, user_string, "administration")))
|
||||||
|
{
|
||||||
|
size_t path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(path_length);
|
||||||
|
snprintf(app_path, path_length, "path:%s", user_app->app);
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
changed = tf_ssb_db_remove_value_from_array_property(ssb, user_string, "apps", user_app->app) || changed;
|
||||||
|
changed = tf_ssb_db_remove_property(ssb, user_string, app_path) || changed;
|
||||||
|
delete->response = changed ? 200 : 404;
|
||||||
|
tf_free(app_path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete->response = 401;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete->response = 404;
|
||||||
|
}
|
||||||
|
tf_free(user_app);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete->response = 401;
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
JS_FreeValue(context, jwt);
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_delete_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
delete_t* delete = user_data;
|
||||||
|
const char* k_payload = tf_http_status_text(delete->response ? delete->response : 404);
|
||||||
|
tf_http_respond(delete->request, delete->response ? delete->response : 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
tf_http_request_unref(delete->request);
|
||||||
|
tf_free((void*)delete->session);
|
||||||
|
tf_free(delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_delete(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
delete_t* delete = tf_malloc(sizeof(delete_t));
|
||||||
|
*delete = (delete_t) {
|
||||||
|
.request = request,
|
||||||
|
.session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"),
|
||||||
|
};
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_delete_work, _httpd_endpoint_delete_after_work, delete);
|
||||||
|
}
|
233
src/httpd.index.c
Normal file
233
src/httpd.index.c
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "file.js.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _index_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
bool found;
|
||||||
|
bool not_modified;
|
||||||
|
bool use_handler;
|
||||||
|
bool use_static;
|
||||||
|
void* data;
|
||||||
|
size_t size;
|
||||||
|
char app_blob_id[k_blob_id_len];
|
||||||
|
const char* file;
|
||||||
|
tf_httpd_user_app_t* user_app;
|
||||||
|
char etag[256];
|
||||||
|
} index_t;
|
||||||
|
|
||||||
|
static bool _has_property(JSContext* context, JSValue object, const char* name)
|
||||||
|
{
|
||||||
|
JSAtom atom = JS_NewAtom(context, name);
|
||||||
|
bool result = JS_HasProperty(context, object, atom) > 0;
|
||||||
|
JS_FreeAtom(context, atom);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_index_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
index_t* data = user_data;
|
||||||
|
data->use_static = true;
|
||||||
|
tf_httpd_user_app_t* user_app = data->user_app;
|
||||||
|
|
||||||
|
size_t app_path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(app_path_length);
|
||||||
|
snprintf(app_path, app_path_length, "path:%s", user_app->app);
|
||||||
|
const char* app_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
||||||
|
tf_free(app_path);
|
||||||
|
|
||||||
|
uint8_t* app_blob = NULL;
|
||||||
|
size_t app_blob_size = 0;
|
||||||
|
|
||||||
|
if (tf_ssb_db_blob_get(ssb, app_blob_id, &app_blob, &app_blob_size))
|
||||||
|
{
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue app = JS_ParseJSON(context, (const char*)app_blob, app_blob_size, NULL);
|
||||||
|
JSValue files = JS_GetPropertyStr(context, app, "files");
|
||||||
|
|
||||||
|
if (!_has_property(context, files, "app.js"))
|
||||||
|
{
|
||||||
|
JSValue index = JS_GetPropertyStr(context, files, "index.html");
|
||||||
|
if (JS_IsString(index))
|
||||||
|
{
|
||||||
|
const char* index_string = JS_ToCString(context, index);
|
||||||
|
tf_ssb_db_blob_get(ssb, index_string, (uint8_t**)&data->data, &data->size);
|
||||||
|
JS_FreeCString(context, index_string);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeValue(context, files);
|
||||||
|
JS_FreeValue(context, app);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
|
||||||
|
tf_free(app_blob);
|
||||||
|
}
|
||||||
|
tf_free((void*)app_blob_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* _replace(const char* original, size_t size, const char* find, const char* replace, size_t* out_size)
|
||||||
|
{
|
||||||
|
char* pos = strstr(original, find);
|
||||||
|
if (!pos)
|
||||||
|
{
|
||||||
|
return tf_strdup(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t replace_length = strlen(replace);
|
||||||
|
size_t find_length = strlen(find);
|
||||||
|
size_t new_size = size + replace_length - find_length;
|
||||||
|
char* buffer = tf_malloc(new_size);
|
||||||
|
memcpy(buffer, original, pos - original);
|
||||||
|
memcpy(buffer + (pos - original), replace, replace_length);
|
||||||
|
memcpy(buffer + (pos - original) + replace_length, pos + find_length, size - (pos - original) - find_length);
|
||||||
|
*out_size = new_size;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* _append_raw(char* document, size_t* current_size, const char* data, size_t size)
|
||||||
|
{
|
||||||
|
document = tf_resize_vec(document, *current_size + size);
|
||||||
|
memcpy(document + *current_size, data, size);
|
||||||
|
document[*current_size + size] = '\0';
|
||||||
|
*current_size += size;
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* _append_encoded(char* document, const char* data, size_t size, size_t* out_size)
|
||||||
|
{
|
||||||
|
size_t current_size = strlen(document);
|
||||||
|
int accum = 0;
|
||||||
|
for (int i = 0; (size_t)i < size; i++)
|
||||||
|
{
|
||||||
|
switch (data[i])
|
||||||
|
{
|
||||||
|
case '"':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, """, strlen("""));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, "'", strlen("'"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, "<", strlen("<"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, ">", strlen(">"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, "&", strlen("&"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*out_size = current_size;
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_index_file_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
|
||||||
|
{
|
||||||
|
index_t* state = user_data;
|
||||||
|
if (result > 0)
|
||||||
|
{
|
||||||
|
char* replacement = tf_strdup("<iframe srcdoc=\"");
|
||||||
|
size_t replacement_size = 0;
|
||||||
|
replacement = _append_encoded(replacement, state->data, state->size, &replacement_size);
|
||||||
|
replacement = _append_raw(replacement, &replacement_size, "\"", 1);
|
||||||
|
|
||||||
|
size_t size = 0;
|
||||||
|
char* document = _replace(data, result, "<iframe", replacement, &size);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
};
|
||||||
|
tf_http_respond(state->request, 200, headers, tf_countof(headers) / 2, document, size);
|
||||||
|
tf_free(replacement);
|
||||||
|
tf_free(document);
|
||||||
|
}
|
||||||
|
tf_free(state->data);
|
||||||
|
tf_free(state->user_app);
|
||||||
|
tf_http_request_unref(state->request);
|
||||||
|
tf_free(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_index_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
index_t* data = user_data;
|
||||||
|
if (data->data)
|
||||||
|
{
|
||||||
|
tf_task_t* task = data->request->user_data;
|
||||||
|
const char* root_path = tf_task_get_root_path(task);
|
||||||
|
size_t size = (root_path ? strlen(root_path) + 1 : 0) + strlen("core/index.html") + 1;
|
||||||
|
char* path = alloca(size);
|
||||||
|
snprintf(path, size, "%s%score/index.html", root_path ? root_path : "", root_path ? "/" : "");
|
||||||
|
tf_file_read(task, path, _httpd_endpoint_app_index_file_read, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_httpd_endpoint_static(data->request);
|
||||||
|
tf_free(data->user_app);
|
||||||
|
tf_http_request_unref(data->request);
|
||||||
|
tf_free(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_app_index(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/");
|
||||||
|
if (!user_app)
|
||||||
|
{
|
||||||
|
return tf_httpd_endpoint_static(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
index_t* data = tf_malloc(sizeof(index_t));
|
||||||
|
(*data) = (index_t) { .request = request, .user_app = user_app };
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_app_index_work, _httpd_endpoint_app_index_after_work, data);
|
||||||
|
}
|
1648
src/httpd.js.c
1648
src/httpd.js.c
File diff suppressed because it is too large
Load Diff
178
src/httpd.js.h
178
src/httpd.js.h
@@ -11,6 +11,14 @@
|
|||||||
** @{
|
** @{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "quickjs.h"
|
||||||
|
|
||||||
|
static const int64_t k_httpd_auth_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
/** A JS context. */
|
/** A JS context. */
|
||||||
typedef struct JSContext JSContext;
|
typedef struct JSContext JSContext;
|
||||||
|
|
||||||
@@ -19,6 +27,27 @@ typedef struct JSContext JSContext;
|
|||||||
*/
|
*/
|
||||||
typedef struct _tf_http_t tf_http_t;
|
typedef struct _tf_http_t tf_http_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** An HTTP request.
|
||||||
|
*/
|
||||||
|
typedef struct _tf_http_request_t tf_http_request_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** An SSB instance.
|
||||||
|
*/
|
||||||
|
typedef struct _tf_ssb_t tf_ssb_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** A user and app name.
|
||||||
|
*/
|
||||||
|
typedef struct _tf_httpd_user_app_t
|
||||||
|
{
|
||||||
|
/** The username. */
|
||||||
|
const char* user;
|
||||||
|
/** The app name. */
|
||||||
|
const char* app;
|
||||||
|
} tf_httpd_user_app_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Register the HTTP script interface. Also registers a number of built-in
|
** Register the HTTP script interface. Also registers a number of built-in
|
||||||
** request handlers. An ongoing project is to move the JS request handlers
|
** request handlers. An ongoing project is to move the JS request handlers
|
||||||
@@ -39,4 +68,153 @@ tf_http_t* tf_httpd_create(JSContext* context);
|
|||||||
*/
|
*/
|
||||||
void tf_httpd_destroy(tf_http_t* http);
|
void tf_httpd_destroy(tf_http_t* http);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Determine a content-type from a file extension.
|
||||||
|
** @param ext The file extension.
|
||||||
|
** @param use_fallback If not found, fallback to application/binary.
|
||||||
|
** @return A MIME type or NULL.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_ext_to_content_type(const char* ext, bool use_fallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Determine a content type from magic bytes.
|
||||||
|
** @param bytes The first bytes of a file.
|
||||||
|
** @param size The length of the bytes.
|
||||||
|
** @return A MIME type or NULL.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_magic_bytes_to_content_type(const uint8_t* bytes, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Respond with a redirect.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
** @return true if redirected.
|
||||||
|
*/
|
||||||
|
bool tf_httpd_redirect(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Parse a username and app from a path like /~user/app/.
|
||||||
|
** @param path The path.
|
||||||
|
** @param expected_suffix A suffix that is required to be on the path, and removed.
|
||||||
|
** @return The user and app. Free with tf_free().
|
||||||
|
*/
|
||||||
|
tf_httpd_user_app_t* tf_httpd_parse_user_app_from_path(const char* path, const char* expected_suffix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Decode form data into key value pairs.
|
||||||
|
** @param data The form data string.
|
||||||
|
** @param length The length of the form data string.
|
||||||
|
** @return Key values pairs terminated by NULL.
|
||||||
|
*/
|
||||||
|
const char** tf_httpd_form_data_decode(const char* data, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Get a form data value from an array of key value pairs produced by tf_httpd_form_data_decode().
|
||||||
|
** @param form_data The form data.
|
||||||
|
** @param key The key for which to fetch the value.
|
||||||
|
** @return the value for the case-insensitive key or NULL.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_form_data_get(const char** form_data, const char* key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Validate a JWT.
|
||||||
|
** @param ssb The SSB instance.
|
||||||
|
** @param context A JS context.
|
||||||
|
** @param jwt The JWT.
|
||||||
|
** @return The JWT contents if valid.
|
||||||
|
*/
|
||||||
|
JSValue tf_httpd_authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt);
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Make a JS response object for a request.
|
||||||
|
** @param context The JS context.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
** @return The respone object.
|
||||||
|
*/
|
||||||
|
JSValue tf_httpd_make_response_object(JSContext* context, tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Check if a name meets requirements.
|
||||||
|
** @param name The name.
|
||||||
|
** @return true if the name is valid.
|
||||||
|
*/
|
||||||
|
bool tf_httpd_is_name_valid(const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Make a header for the session cookie.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
** @param session_cookie The session cookie.
|
||||||
|
** @return The header.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Make a JWT for the session.
|
||||||
|
** @param context A JS context.
|
||||||
|
** @param ssb The SSB instance.
|
||||||
|
** @param name The username.
|
||||||
|
** @return The JWT.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_make_session_jwt(JSContext* context, tf_ssb_t* ssb, const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Serve a static file.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_static(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** View a blob.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_view(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Save a blob or app.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_save(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Delete a blob or app.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_delete(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_app(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App index endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_app_index(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App WebSocket.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_app_socket(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Login endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_login(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Auto-login endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_login_auto(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Logout endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_logout(tf_http_request_t* request);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
537
src/httpd.login.c
Normal file
537
src/httpd.login.c
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "file.js.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include "ow-crypt.h"
|
||||||
|
#include "sodium/utils.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _login_request_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
const char* name;
|
||||||
|
const char* error;
|
||||||
|
const char* settings;
|
||||||
|
const char* code_of_conduct;
|
||||||
|
bool have_administrator;
|
||||||
|
bool session_is_new;
|
||||||
|
|
||||||
|
char location_header[1024];
|
||||||
|
const char* set_cookie_header;
|
||||||
|
|
||||||
|
int pending;
|
||||||
|
} login_request_t;
|
||||||
|
|
||||||
|
const char* tf_httpd_make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie)
|
||||||
|
{
|
||||||
|
const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly";
|
||||||
|
int length = session_cookie ? snprintf(NULL, 0, k_pattern, session_cookie, k_httpd_auth_refresh_interval, request->is_tls ? "Secure; " : "") : 0;
|
||||||
|
char* cookie = length ? tf_malloc(length + 1) : NULL;
|
||||||
|
if (cookie)
|
||||||
|
{
|
||||||
|
snprintf(cookie, length + 1, k_pattern, session_cookie, k_httpd_auth_refresh_interval, request->is_tls ? "Secure; " : "");
|
||||||
|
}
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _login_release(login_request_t* login)
|
||||||
|
{
|
||||||
|
int ref_count = --login->pending;
|
||||||
|
if (ref_count == 0)
|
||||||
|
{
|
||||||
|
tf_free((void*)login->name);
|
||||||
|
tf_free((void*)login->code_of_conduct);
|
||||||
|
tf_free((void*)login->set_cookie_header);
|
||||||
|
tf_free(login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
|
||||||
|
{
|
||||||
|
login_request_t* login = user_data;
|
||||||
|
tf_http_request_t* request = login->request;
|
||||||
|
if (result >= 0)
|
||||||
|
{
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
"Set-Cookie",
|
||||||
|
login->set_cookie_header ? login->set_cookie_header : "",
|
||||||
|
};
|
||||||
|
const char* replace_me = "$AUTH_DATA";
|
||||||
|
const char* auth = strstr(data, replace_me);
|
||||||
|
if (auth)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_task_get_context(task);
|
||||||
|
JSValue object = JS_NewObject(context);
|
||||||
|
JS_SetPropertyStr(context, object, "session_is_new", JS_NewBool(context, login->session_is_new));
|
||||||
|
JS_SetPropertyStr(context, object, "name", login->name ? JS_NewString(context, login->name) : JS_UNDEFINED);
|
||||||
|
JS_SetPropertyStr(context, object, "error", login->error ? JS_NewString(context, login->error) : JS_UNDEFINED);
|
||||||
|
JS_SetPropertyStr(context, object, "code_of_conduct", login->code_of_conduct ? JS_NewString(context, login->code_of_conduct) : JS_UNDEFINED);
|
||||||
|
JS_SetPropertyStr(context, object, "have_administrator", JS_NewBool(context, login->have_administrator));
|
||||||
|
JSValue object_json = JS_JSONStringify(context, object, JS_NULL, JS_NULL);
|
||||||
|
size_t json_length = 0;
|
||||||
|
const char* json = JS_ToCStringLen(context, &json_length, object_json);
|
||||||
|
|
||||||
|
char* copy = tf_malloc(result + json_length);
|
||||||
|
int replace_start = (auth - (const char*)data);
|
||||||
|
int replace_end = (auth - (const char*)data) + (int)strlen(replace_me);
|
||||||
|
memcpy(copy, data, replace_start);
|
||||||
|
memcpy(copy + replace_start, json, json_length);
|
||||||
|
memcpy(copy + replace_start + json_length, ((const char*)data) + replace_end, result - replace_end);
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, copy, replace_start + json_length + (result - replace_end));
|
||||||
|
tf_free(copy);
|
||||||
|
|
||||||
|
JS_FreeCString(context, json);
|
||||||
|
JS_FreeValue(context, object_json);
|
||||||
|
JS_FreeValue(context, object);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
_login_release(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _session_is_authenticated_as_user(JSContext* context, JSValue session)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
JSValue user = JS_GetPropertyStr(context, session, "name");
|
||||||
|
const char* user_string = JS_ToCString(context, user);
|
||||||
|
result = user_string && strcmp(user_string, "guest") != 0;
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _make_administrator_if_first(tf_ssb_t* ssb, JSContext* context, const char* account_name_copy, bool may_become_first_admin)
|
||||||
|
{
|
||||||
|
const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
|
||||||
|
JSValue settings_value = settings && *settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
|
||||||
|
if (JS_IsUndefined(settings_value))
|
||||||
|
{
|
||||||
|
settings_value = JS_NewObject(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool have_administrator = false;
|
||||||
|
JSValue permissions = JS_GetPropertyStr(context, settings_value, "permissions");
|
||||||
|
|
||||||
|
JSPropertyEnum* ptab = NULL;
|
||||||
|
uint32_t plen = 0;
|
||||||
|
JS_GetOwnPropertyNames(context, &ptab, &plen, permissions, JS_GPN_STRING_MASK);
|
||||||
|
for (int i = 0; i < (int)plen; i++)
|
||||||
|
{
|
||||||
|
JSPropertyDescriptor desc = { 0 };
|
||||||
|
if (JS_GetOwnProperty(context, &desc, permissions, ptab[i].atom) == 1)
|
||||||
|
{
|
||||||
|
int permission_length = tf_util_get_length(context, desc.value);
|
||||||
|
for (int i = 0; i < permission_length; i++)
|
||||||
|
{
|
||||||
|
JSValue entry = JS_GetPropertyUint32(context, desc.value, i);
|
||||||
|
const char* permission = JS_ToCString(context, entry);
|
||||||
|
if (permission && strcmp(permission, "administration") == 0)
|
||||||
|
{
|
||||||
|
have_administrator = true;
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, permission);
|
||||||
|
JS_FreeValue(context, entry);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, desc.setter);
|
||||||
|
JS_FreeValue(context, desc.getter);
|
||||||
|
JS_FreeValue(context, desc.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < plen; ++i)
|
||||||
|
{
|
||||||
|
JS_FreeAtom(context, ptab[i].atom);
|
||||||
|
}
|
||||||
|
js_free(context, ptab);
|
||||||
|
|
||||||
|
if (!have_administrator && may_become_first_admin)
|
||||||
|
{
|
||||||
|
if (JS_IsUndefined(permissions))
|
||||||
|
{
|
||||||
|
permissions = JS_NewObject(context);
|
||||||
|
JS_SetPropertyStr(context, settings_value, "permissions", JS_DupValue(context, permissions));
|
||||||
|
}
|
||||||
|
JSValue user = JS_GetPropertyStr(context, permissions, account_name_copy);
|
||||||
|
if (JS_IsUndefined(user))
|
||||||
|
{
|
||||||
|
user = JS_NewArray(context);
|
||||||
|
JS_SetPropertyStr(context, permissions, account_name_copy, JS_DupValue(context, user));
|
||||||
|
}
|
||||||
|
JS_SetPropertyUint32(context, user, tf_util_get_length(context, user), JS_NewString(context, "administration"));
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
|
||||||
|
JSValue settings_json = JS_JSONStringify(context, settings_value, JS_NULL, JS_NULL);
|
||||||
|
const char* settings_string = JS_ToCString(context, settings_json);
|
||||||
|
tf_ssb_db_set_property(ssb, "core", "settings", settings_string);
|
||||||
|
JS_FreeCString(context, settings_string);
|
||||||
|
JS_FreeValue(context, settings_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeValue(context, permissions);
|
||||||
|
JS_FreeValue(context, settings_value);
|
||||||
|
tf_free((void*)settings);
|
||||||
|
return have_administrator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _verify_password(const char* password, const char* hash)
|
||||||
|
{
|
||||||
|
char buffer[7 + 22 + 31 + 1];
|
||||||
|
const char* out_hash = crypt_rn(password, hash, buffer, sizeof(buffer));
|
||||||
|
return out_hash && strcmp(hash, out_hash) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
login_request_t* login = user_data;
|
||||||
|
tf_http_request_t* request = login->request;
|
||||||
|
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
|
||||||
|
const char** form_data = tf_httpd_form_data_decode(request->query, request->query ? strlen(request->query) : 0);
|
||||||
|
const char* account_name_copy = NULL;
|
||||||
|
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
|
||||||
|
|
||||||
|
if (_session_is_authenticated_as_user(context, jwt))
|
||||||
|
{
|
||||||
|
const char* return_url = tf_httpd_form_data_get(form_data, "return");
|
||||||
|
if (return_url)
|
||||||
|
{
|
||||||
|
tf_string_set(login->location_header, sizeof(login->location_header), return_url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
|
||||||
|
}
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* send_session = tf_strdup(session);
|
||||||
|
bool session_is_new = false;
|
||||||
|
const char* login_error = NULL;
|
||||||
|
bool may_become_first_admin = false;
|
||||||
|
if (strcmp(request->method, "POST") == 0)
|
||||||
|
{
|
||||||
|
session_is_new = true;
|
||||||
|
const char** post_form_data = tf_httpd_form_data_decode(request->body, request->content_length);
|
||||||
|
const char* submit = tf_httpd_form_data_get(post_form_data, "submit");
|
||||||
|
if (submit && strcmp(submit, "Login") == 0)
|
||||||
|
{
|
||||||
|
const char* account_name = tf_httpd_form_data_get(post_form_data, "name");
|
||||||
|
account_name_copy = tf_strdup(account_name);
|
||||||
|
const char* password = tf_httpd_form_data_get(post_form_data, "password");
|
||||||
|
const char* new_password = tf_httpd_form_data_get(post_form_data, "new_password");
|
||||||
|
const char* confirm = tf_httpd_form_data_get(post_form_data, "confirm");
|
||||||
|
const char* change = tf_httpd_form_data_get(post_form_data, "change");
|
||||||
|
const char* form_register = tf_httpd_form_data_get(post_form_data, "register");
|
||||||
|
char account_passwd[256] = { 0 };
|
||||||
|
bool have_account = tf_ssb_db_get_account_password_hash(ssb, tf_httpd_form_data_get(post_form_data, "name"), account_passwd, sizeof(account_passwd));
|
||||||
|
|
||||||
|
if (form_register && strcmp(form_register, "1") == 0)
|
||||||
|
{
|
||||||
|
bool registered = false;
|
||||||
|
if (!tf_httpd_is_name_valid(account_name))
|
||||||
|
{
|
||||||
|
login_error = "Invalid username. Usernames must contain only letters from the English alphabet and digits and must start with a letter.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!have_account && tf_httpd_is_name_valid(account_name) && password && confirm && strcmp(password, confirm) == 0)
|
||||||
|
{
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, account_name, password);
|
||||||
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
|
if (registered)
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, account_name);
|
||||||
|
may_become_first_admin = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!registered && !login_error)
|
||||||
|
{
|
||||||
|
login_error = "Error registering account.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (change && strcmp(change, "1") == 0)
|
||||||
|
{
|
||||||
|
bool set = false;
|
||||||
|
if (have_account && tf_httpd_is_name_valid(account_name) && new_password && confirm && strcmp(new_password, confirm) == 0 &&
|
||||||
|
_verify_password(password, account_passwd))
|
||||||
|
{
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
set = tf_ssb_db_set_account_password(tf_ssb_get_loop(ssb), db, context, account_name, new_password);
|
||||||
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
|
if (set)
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, account_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!set)
|
||||||
|
{
|
||||||
|
login_error = "Error changing password.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (have_account && *account_passwd && _verify_password(password, account_passwd))
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, account_name);
|
||||||
|
may_become_first_admin = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
login_error = "Invalid username or password.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, "guest");
|
||||||
|
}
|
||||||
|
tf_free(post_form_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool have_administrator = _make_administrator_if_first(ssb, context, account_name_copy, may_become_first_admin);
|
||||||
|
|
||||||
|
if (session_is_new && tf_httpd_form_data_get(form_data, "return") && !login_error)
|
||||||
|
{
|
||||||
|
const char* return_url = tf_httpd_form_data_get(form_data, "return");
|
||||||
|
if (return_url)
|
||||||
|
{
|
||||||
|
tf_string_set(login->location_header, sizeof(login->location_header), return_url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
|
||||||
|
}
|
||||||
|
login->set_cookie_header = tf_httpd_make_set_session_cookie_header(request, send_session);
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
login->name = account_name_copy;
|
||||||
|
login->error = login_error;
|
||||||
|
login->set_cookie_header = tf_httpd_make_set_session_cookie_header(request, send_session);
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
login->session_is_new = session_is_new;
|
||||||
|
login->have_administrator = have_administrator;
|
||||||
|
login->settings = tf_ssb_db_get_property(ssb, "core", "settings");
|
||||||
|
|
||||||
|
if (login->settings)
|
||||||
|
{
|
||||||
|
JSValue settings_value = JS_ParseJSON(context, login->settings, strlen(login->settings), NULL);
|
||||||
|
JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct");
|
||||||
|
const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value);
|
||||||
|
const char* result = tf_strdup(code_of_conduct);
|
||||||
|
JS_FreeCString(context, code_of_conduct);
|
||||||
|
JS_FreeValue(context, code_of_conduct_value);
|
||||||
|
JS_FreeValue(context, settings_value);
|
||||||
|
tf_free((void*)login->settings);
|
||||||
|
login->settings = NULL;
|
||||||
|
login->code_of_conduct = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
login->pending++;
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_file_read(login->request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login);
|
||||||
|
|
||||||
|
account_name_copy = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
tf_free((void*)session);
|
||||||
|
tf_free(form_data);
|
||||||
|
tf_free((void*)account_name_copy);
|
||||||
|
JS_FreeValue(context, jwt);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_login_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
login_request_t* login = user_data;
|
||||||
|
tf_http_request_t* request = login->request;
|
||||||
|
if (login->pending == 1)
|
||||||
|
{
|
||||||
|
if (*login->location_header)
|
||||||
|
{
|
||||||
|
const char* headers[] = {
|
||||||
|
"Location",
|
||||||
|
login->location_header,
|
||||||
|
"Set-Cookie",
|
||||||
|
login->set_cookie_header ? login->set_cookie_header : "",
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
_login_release(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_login(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
login_request_t* login = tf_malloc(sizeof(login_request_t));
|
||||||
|
*login = (login_request_t) {
|
||||||
|
.request = request,
|
||||||
|
};
|
||||||
|
login->pending++;
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_login_work, _httpd_endpoint_login_after_work, login);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_logout(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
const char* k_set_cookie = request->is_tls ? "session=; path=/; Secure; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly"
|
||||||
|
: "session=; path=/; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly";
|
||||||
|
const char* k_location_format = "/login%s%s";
|
||||||
|
int length = snprintf(NULL, 0, k_location_format, request->query ? "?" : "", request->query);
|
||||||
|
char* location = alloca(length + 1);
|
||||||
|
snprintf(location, length + 1, k_location_format, request->query ? "?" : "", request->query ? request->query : "");
|
||||||
|
const char* headers[] = {
|
||||||
|
"Set-Cookie",
|
||||||
|
k_set_cookie,
|
||||||
|
"Location",
|
||||||
|
location,
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _auto_login_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
bool autologin;
|
||||||
|
const char* users;
|
||||||
|
} auto_login_t;
|
||||||
|
|
||||||
|
static void _httpd_auto_login_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
auto_login_t* request = user_data;
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
|
tf_ssb_db_get_global_setting_bool(db, "autologin", &request->autologin);
|
||||||
|
tf_ssb_release_db_reader(ssb, db);
|
||||||
|
|
||||||
|
if (request->autologin)
|
||||||
|
{
|
||||||
|
request->users = tf_ssb_db_get_property(ssb, "auth", "users");
|
||||||
|
if (request->users && strcmp(request->users, "[]") == 0)
|
||||||
|
{
|
||||||
|
tf_free((void*)request->users);
|
||||||
|
request->users = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request->users)
|
||||||
|
{
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
static const char* k_account_name = "mobile";
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
bool registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, k_account_name, k_account_name);
|
||||||
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
|
if (registered)
|
||||||
|
{
|
||||||
|
_make_administrator_if_first(ssb, context, k_account_name, true);
|
||||||
|
}
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
|
||||||
|
request->users = tf_ssb_db_get_property(ssb, "auth", "users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_auto_login_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
auto_login_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
const char* session_token = NULL;
|
||||||
|
if (!work->autologin)
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(work->request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (work->users)
|
||||||
|
{
|
||||||
|
JSValue json = JS_ParseJSON(context, work->users, strlen(work->users), NULL);
|
||||||
|
JSValue user = JS_GetPropertyUint32(context, json, 0);
|
||||||
|
const char* user_string = JS_ToCString(context, user);
|
||||||
|
session_token = tf_httpd_make_session_jwt(context, ssb, user_string);
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
JS_FreeValue(context, json);
|
||||||
|
}
|
||||||
|
if (session_token)
|
||||||
|
{
|
||||||
|
const char* cookie = tf_httpd_make_set_session_cookie_header(work->request, session_token);
|
||||||
|
tf_free((void*)session_token);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Set-Cookie",
|
||||||
|
cookie,
|
||||||
|
"Location",
|
||||||
|
"/",
|
||||||
|
};
|
||||||
|
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||||
|
tf_free((void*)cookie);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* headers[] = {
|
||||||
|
"Location",
|
||||||
|
"/",
|
||||||
|
};
|
||||||
|
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tf_http_request_unref(work->request);
|
||||||
|
tf_free((void*)work->users);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_login_auto(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
|
||||||
|
auto_login_t* work = tf_malloc(sizeof(auto_login_t));
|
||||||
|
*work = (auto_login_t) { .request = request };
|
||||||
|
tf_ssb_run_work(ssb, _httpd_auto_login_work, _httpd_auto_login_after_work, work);
|
||||||
|
}
|
181
src/httpd.save.c
Normal file
181
src/httpd.save.c
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
typedef struct _save_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
int response;
|
||||||
|
char blob_id[k_blob_id_len];
|
||||||
|
} save_t;
|
||||||
|
|
||||||
|
static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
save_t* save = user_data;
|
||||||
|
tf_http_request_t* request = save->request;
|
||||||
|
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
|
||||||
|
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
|
||||||
|
JSValue user = JS_GetPropertyStr(context, jwt, "name");
|
||||||
|
const char* user_string = JS_ToCString(context, user);
|
||||||
|
|
||||||
|
if (user_string && tf_httpd_is_name_valid(user_string))
|
||||||
|
{
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/save");
|
||||||
|
if (user_app)
|
||||||
|
{
|
||||||
|
if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, NULL, user_string, "administration")))
|
||||||
|
{
|
||||||
|
size_t path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(path_length);
|
||||||
|
snprintf(app_path, path_length, "path:%s", user_app->app);
|
||||||
|
|
||||||
|
const char* old_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
||||||
|
|
||||||
|
JSValue new_app = JS_ParseJSON(context, request->body, request->content_length, NULL);
|
||||||
|
tf_util_report_error(context, new_app);
|
||||||
|
if (JS_IsObject(new_app))
|
||||||
|
{
|
||||||
|
uint8_t* old_blob = NULL;
|
||||||
|
size_t old_blob_size = 0;
|
||||||
|
if (tf_ssb_db_blob_get(ssb, old_blob_id, &old_blob, &old_blob_size))
|
||||||
|
{
|
||||||
|
JSValue old_app = JS_ParseJSON(context, (const char*)old_blob, old_blob_size, NULL);
|
||||||
|
if (JS_IsObject(old_app))
|
||||||
|
{
|
||||||
|
JSAtom previous = JS_NewAtom(context, "previous");
|
||||||
|
JS_DeleteProperty(context, old_app, previous, 0);
|
||||||
|
JS_DeleteProperty(context, new_app, previous, 0);
|
||||||
|
|
||||||
|
JSValue old_app_json = JS_JSONStringify(context, old_app, JS_NULL, JS_NULL);
|
||||||
|
JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
|
||||||
|
const char* old_app_str = JS_ToCString(context, old_app_json);
|
||||||
|
const char* new_app_str = JS_ToCString(context, new_app_json);
|
||||||
|
|
||||||
|
if (old_app_str && new_app_str && strcmp(old_app_str, new_app_str) == 0)
|
||||||
|
{
|
||||||
|
snprintf(save->blob_id, sizeof(save->blob_id), "/%s", old_blob_id);
|
||||||
|
save->response = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(context, old_app_str);
|
||||||
|
JS_FreeCString(context, new_app_str);
|
||||||
|
JS_FreeValue(context, old_app_json);
|
||||||
|
JS_FreeValue(context, new_app_json);
|
||||||
|
JS_FreeAtom(context, previous);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, old_app);
|
||||||
|
tf_free(old_blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save->response)
|
||||||
|
{
|
||||||
|
if (old_blob_id)
|
||||||
|
{
|
||||||
|
JS_SetPropertyStr(context, new_app, "previous", JS_NewString(context, old_blob_id));
|
||||||
|
}
|
||||||
|
JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
|
||||||
|
size_t new_app_length = 0;
|
||||||
|
const char* new_app_str = JS_ToCStringLen(context, &new_app_length, new_app_json);
|
||||||
|
|
||||||
|
char blob_id[k_blob_id_len] = { 0 };
|
||||||
|
if (tf_ssb_db_blob_store(ssb, (const uint8_t*)new_app_str, new_app_length, blob_id, sizeof(blob_id), NULL) &&
|
||||||
|
tf_ssb_db_set_property(ssb, user_app->user, app_path, blob_id))
|
||||||
|
{
|
||||||
|
tf_ssb_db_add_value_to_array_property(ssb, user_app->user, "apps", user_app->app);
|
||||||
|
tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
|
||||||
|
save->response = 200;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_printf("Blob store or property set failed.\n");
|
||||||
|
save->response = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(context, new_app_str);
|
||||||
|
JS_FreeValue(context, new_app_json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 400;
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, new_app);
|
||||||
|
|
||||||
|
tf_free(app_path);
|
||||||
|
tf_free((void*)old_blob_id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 403;
|
||||||
|
}
|
||||||
|
tf_free(user_app);
|
||||||
|
}
|
||||||
|
else if (strcmp(request->path, "/save") == 0)
|
||||||
|
{
|
||||||
|
char blob_id[k_blob_id_len] = { 0 };
|
||||||
|
if (tf_ssb_db_blob_store(ssb, request->body, request->content_length, blob_id, sizeof(blob_id), NULL))
|
||||||
|
{
|
||||||
|
tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
|
||||||
|
save->response = 200;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_printf("Blob store failed.\n");
|
||||||
|
save->response = 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 401;
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_free((void*)session);
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
JS_FreeValue(context, jwt);
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_save_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
save_t* save = user_data;
|
||||||
|
tf_http_request_t* request = save->request;
|
||||||
|
if (*save->blob_id)
|
||||||
|
{
|
||||||
|
char body[256] = "";
|
||||||
|
int length = snprintf(body, sizeof(body), "/%s", save->blob_id);
|
||||||
|
tf_http_respond(request, 200, NULL, 0, body, length);
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
tf_free(save);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_save(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
save_t* save = tf_malloc(sizeof(save_t));
|
||||||
|
*save = (save_t) {
|
||||||
|
.request = request,
|
||||||
|
};
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_save_work, _httpd_endpoint_save_after_work, save);
|
||||||
|
}
|
202
src/httpd.static.c
Normal file
202
src/httpd.static.c
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "file.js.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _http_file_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
char etag[512];
|
||||||
|
} http_file_t;
|
||||||
|
|
||||||
|
static bool _ends_with(const char* a, const char* suffix)
|
||||||
|
{
|
||||||
|
if (!a || !suffix)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t alen = strlen(a);
|
||||||
|
size_t suffixlen = strlen(suffix);
|
||||||
|
return alen >= suffixlen && strcmp(a + alen - suffixlen, suffix) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* _after(const char* text, const char* prefix)
|
||||||
|
{
|
||||||
|
size_t prefix_length = strlen(prefix);
|
||||||
|
if (text && strncmp(text, prefix, prefix_length) == 0)
|
||||||
|
{
|
||||||
|
return text + prefix_length;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double _time_spec_to_double(const uv_timespec_t* time_spec)
|
||||||
|
{
|
||||||
|
return (double)time_spec->tv_sec + (double)(time_spec->tv_nsec) / 1e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
|
||||||
|
{
|
||||||
|
http_file_t* file = user_data;
|
||||||
|
tf_http_request_t* request = file->request;
|
||||||
|
if (result >= 0)
|
||||||
|
{
|
||||||
|
if (strcmp(path, "core/tfrpc.js") == 0 || _ends_with(path, "core/tfrpc.js"))
|
||||||
|
{
|
||||||
|
const char* content_type = tf_httpd_ext_to_content_type(strrchr(path, '.'), true);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
content_type,
|
||||||
|
"etag",
|
||||||
|
file->etag,
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
"null",
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* content_type = tf_httpd_ext_to_content_type(strrchr(path, '.'), true);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
content_type,
|
||||||
|
"etag",
|
||||||
|
file->etag,
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
tf_free(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_static_stat(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data)
|
||||||
|
{
|
||||||
|
tf_http_request_t* request = user_data;
|
||||||
|
const char* match = tf_http_request_get_header(request, "if-none-match");
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char etag[512];
|
||||||
|
snprintf(etag, sizeof(etag), "\"%f_%zd\"", _time_spec_to_double(&stat->st_mtim), (size_t)stat->st_size);
|
||||||
|
if (match && strcmp(match, etag) == 0)
|
||||||
|
{
|
||||||
|
tf_http_respond(request, 304, NULL, 0, NULL, 0);
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
http_file_t* file = tf_malloc(sizeof(http_file_t));
|
||||||
|
*file = (http_file_t) { .request = request };
|
||||||
|
static_assert(sizeof(file->etag) == sizeof(etag), "Size mismatch");
|
||||||
|
memcpy(file->etag, etag, sizeof(etag));
|
||||||
|
tf_file_read(task, path, _httpd_endpoint_static_read, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_static(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
if (request->path && strncmp(request->path, "/.well-known/", strlen("/.well-known/")) && tf_httpd_redirect(request))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* k_static_files[] = {
|
||||||
|
"index.html",
|
||||||
|
"client.js",
|
||||||
|
"tildefriends.svg",
|
||||||
|
"jszip.min.js",
|
||||||
|
"style.css",
|
||||||
|
"tfrpc.js",
|
||||||
|
"w3.css",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* k_map[][2] = {
|
||||||
|
{ "/static/", "core/" },
|
||||||
|
{ "/lit/", "deps/lit/" },
|
||||||
|
{ "/codemirror/", "deps/codemirror/" },
|
||||||
|
{ "/prettier/", "deps/prettier/" },
|
||||||
|
{ "/speedscope/", "deps/speedscope/" },
|
||||||
|
{ "/.well-known/", "data/global/.well-known/" },
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_core = false;
|
||||||
|
const char* after = NULL;
|
||||||
|
const char* file_path = NULL;
|
||||||
|
for (int i = 0; i < tf_countof(k_map) && !after; i++)
|
||||||
|
{
|
||||||
|
const char* next_after = _after(request->path, k_map[i][0]);
|
||||||
|
if (next_after)
|
||||||
|
{
|
||||||
|
after = next_after;
|
||||||
|
file_path = k_map[i][1];
|
||||||
|
is_core = after && i == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!after || !*after) && request->path[strlen(request->path) - 1] == '/')
|
||||||
|
{
|
||||||
|
after = "index.html";
|
||||||
|
if (!file_path)
|
||||||
|
{
|
||||||
|
file_path = "core/";
|
||||||
|
is_core = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!after || strstr(after, ".."))
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_core)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < tf_countof(k_static_files); i++)
|
||||||
|
{
|
||||||
|
if (strcmp(after, k_static_files[i]) == 0)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
const char* root_path = tf_task_get_root_path(task);
|
||||||
|
size_t size = (root_path ? strlen(root_path) + 1 : 0) + strlen(file_path) + strlen(after) + 1;
|
||||||
|
char* path = alloca(size);
|
||||||
|
snprintf(path, size, "%s%s%s%s", root_path ? root_path : "", root_path ? "/" : "", file_path, after);
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
|
||||||
|
}
|
140
src/httpd.view.c
Normal file
140
src/httpd.view.c
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
typedef struct _view_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
const char** form_data;
|
||||||
|
void* data;
|
||||||
|
size_t size;
|
||||||
|
char etag[256];
|
||||||
|
char notify_want_blob_id[k_blob_id_len];
|
||||||
|
bool not_modified;
|
||||||
|
} view_t;
|
||||||
|
|
||||||
|
static bool _is_filename_safe(const char* filename)
|
||||||
|
{
|
||||||
|
if (!filename)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (const char* p = filename; *p; p++)
|
||||||
|
{
|
||||||
|
if ((*p <= 'a' && *p >= 'z') && (*p <= 'A' && *p >= 'Z') && (*p <= '0' && *p >= '9') && *p != '.' && *p != '-' && *p != '_')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strlen(filename) < 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
view_t* view = user_data;
|
||||||
|
tf_http_request_t* request = view->request;
|
||||||
|
char blob_id[k_blob_id_len] = "";
|
||||||
|
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/view");
|
||||||
|
if (user_app)
|
||||||
|
{
|
||||||
|
size_t app_path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(app_path_length);
|
||||||
|
snprintf(app_path, app_path_length, "path:%s", user_app->app);
|
||||||
|
const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
||||||
|
tf_string_set(blob_id, sizeof(blob_id), value);
|
||||||
|
tf_free(app_path);
|
||||||
|
tf_free((void*)value);
|
||||||
|
}
|
||||||
|
else if (request->path[0] == '/' && request->path[1] == '&')
|
||||||
|
{
|
||||||
|
snprintf(blob_id, sizeof(blob_id), "%.*s", (int)(strlen(request->path) - strlen("/view") - 1), request->path + 1);
|
||||||
|
}
|
||||||
|
tf_free(user_app);
|
||||||
|
|
||||||
|
if (*blob_id)
|
||||||
|
{
|
||||||
|
snprintf(view->etag, sizeof(view->etag), "\"%s\"", blob_id);
|
||||||
|
const char* if_none_match = tf_http_request_get_header(request, "if-none-match");
|
||||||
|
char match[258];
|
||||||
|
snprintf(match, sizeof(match), "\"%s\"", blob_id);
|
||||||
|
if (if_none_match && strcmp(if_none_match, match) == 0)
|
||||||
|
{
|
||||||
|
view->not_modified = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size))
|
||||||
|
{
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
tf_ssb_db_add_blob_wants(db, blob_id);
|
||||||
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
|
tf_string_set(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), blob_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
view_t* view = user_data;
|
||||||
|
const char* filename = tf_httpd_form_data_get(view->form_data, "filename");
|
||||||
|
if (!_is_filename_safe(filename))
|
||||||
|
{
|
||||||
|
filename = NULL;
|
||||||
|
}
|
||||||
|
char content_disposition[512] = "";
|
||||||
|
if (filename)
|
||||||
|
{
|
||||||
|
snprintf(content_disposition, sizeof(content_disposition), "attachment; filename=%s", filename);
|
||||||
|
}
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Security-Policy",
|
||||||
|
"sandbox allow-downloads allow-top-navigation-by-user-activation",
|
||||||
|
"Content-Type",
|
||||||
|
view->data ? tf_httpd_magic_bytes_to_content_type(view->data, view->size) : "text/plain",
|
||||||
|
"etag",
|
||||||
|
view->etag,
|
||||||
|
filename ? "Content-Disposition" : NULL,
|
||||||
|
filename ? content_disposition : NULL,
|
||||||
|
};
|
||||||
|
int count = filename ? tf_countof(headers) / 2 : (tf_countof(headers) / 2 - 1);
|
||||||
|
if (view->not_modified)
|
||||||
|
{
|
||||||
|
tf_http_respond(view->request, 304, headers, count, NULL, 0);
|
||||||
|
}
|
||||||
|
else if (view->data)
|
||||||
|
{
|
||||||
|
tf_http_respond(view->request, 200, headers, count, view->data, view->size);
|
||||||
|
tf_free(view->data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*view->notify_want_blob_id)
|
||||||
|
{
|
||||||
|
tf_ssb_notify_blob_want_added(ssb, view->notify_want_blob_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_free(view->form_data);
|
||||||
|
tf_http_request_unref(view->request);
|
||||||
|
tf_free(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_view(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
view_t* view = tf_malloc(sizeof(view_t));
|
||||||
|
*view = (view_t) { .request = request, .form_data = tf_httpd_form_data_decode(request->query, request->query ? strlen(request->query) : 0) };
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_view_work, _httpd_endpoint_view_after_work, view);
|
||||||
|
}
|
@@ -13,13 +13,13 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.0.32.1</string>
|
<string>0.2025.9</string>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iPhoneOS</string>
|
<string>iPhoneOS</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>15</string>
|
<string>17</string>
|
||||||
<key>DTPlatformName</key>
|
<key>DTPlatformName</key>
|
||||||
<string>iphoneos</string>
|
<string>iphoneos</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
@@ -1442,7 +1442,7 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
|
|||||||
int result = -1;
|
int result = -1;
|
||||||
tf_task_t* task = tf_task_create();
|
tf_task_t* task = tf_task_create();
|
||||||
tf_task_set_trusted(task, true);
|
tf_task_set_trusted(task, true);
|
||||||
tf_printf("setting zip path to %s\n", args->zip);
|
tf_printf("Zip path: %s\n", args->zip);
|
||||||
tf_task_set_zip_path(task, args->zip);
|
tf_task_set_zip_path(task, args->zip);
|
||||||
tf_task_set_ssb_network_key(task, args->network_key);
|
tf_task_set_ssb_network_key(task, args->network_key);
|
||||||
tf_task_set_args(task, args->args);
|
tf_task_set_args(task, args->args);
|
||||||
@@ -1487,7 +1487,7 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tf_printf("Using %s as the working directory.\n", cwd);
|
tf_printf("Root path: %s\n", cwd);
|
||||||
}
|
}
|
||||||
if (!*cwd)
|
if (!*cwd)
|
||||||
{
|
{
|
||||||
|
88
src/ssb.c
88
src/ssb.c
@@ -133,6 +133,15 @@ typedef struct _tf_ssb_message_added_callback_node_t
|
|||||||
tf_ssb_message_added_callback_node_t* next;
|
tf_ssb_message_added_callback_node_t* next;
|
||||||
} tf_ssb_message_added_callback_node_t;
|
} tf_ssb_message_added_callback_node_t;
|
||||||
|
|
||||||
|
typedef struct _tf_ssb_blob_stored_callback_node_t tf_ssb_blob_stored_callback_node_t;
|
||||||
|
typedef struct _tf_ssb_blob_stored_callback_node_t
|
||||||
|
{
|
||||||
|
tf_ssb_blob_stored_callback_t* callback;
|
||||||
|
tf_ssb_callback_cleanup_t* cleanup;
|
||||||
|
void* user_data;
|
||||||
|
tf_ssb_blob_stored_callback_node_t* next;
|
||||||
|
} tf_ssb_blob_stored_callback_node_t;
|
||||||
|
|
||||||
typedef struct _tf_ssb_blob_want_added_callback_node_t tf_ssb_blob_want_added_callback_node_t;
|
typedef struct _tf_ssb_blob_want_added_callback_node_t tf_ssb_blob_want_added_callback_node_t;
|
||||||
typedef struct _tf_ssb_blob_want_added_callback_node_t
|
typedef struct _tf_ssb_blob_want_added_callback_node_t
|
||||||
{
|
{
|
||||||
@@ -235,6 +244,9 @@ typedef struct _tf_ssb_t
|
|||||||
tf_ssb_message_added_callback_node_t* message_added;
|
tf_ssb_message_added_callback_node_t* message_added;
|
||||||
int message_added_count;
|
int message_added_count;
|
||||||
|
|
||||||
|
tf_ssb_blob_stored_callback_node_t* blob_stored;
|
||||||
|
int blob_stored_count;
|
||||||
|
|
||||||
tf_ssb_blob_want_added_callback_node_t* blob_want_added;
|
tf_ssb_blob_want_added_callback_node_t* blob_want_added;
|
||||||
int blob_want_added_count;
|
int blob_want_added_count;
|
||||||
|
|
||||||
@@ -977,7 +989,6 @@ bool tf_ssb_connection_rpc_send_error_method_not_allowed(tf_ssb_connection_t* co
|
|||||||
{
|
{
|
||||||
char buffer[1024] = "";
|
char buffer[1024] = "";
|
||||||
snprintf(buffer, sizeof(buffer), "method '%s' is not in list of allowed methods", name);
|
snprintf(buffer, sizeof(buffer), "method '%s' is not in list of allowed methods", name);
|
||||||
tf_printf("%s\n", buffer);
|
|
||||||
return tf_ssb_connection_rpc_send_error(connection, flags, request_number, buffer);
|
return tf_ssb_connection_rpc_send_error(connection, flags, request_number, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2742,6 +2753,17 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
|||||||
}
|
}
|
||||||
tf_free(node);
|
tf_free(node);
|
||||||
}
|
}
|
||||||
|
while (ssb->blob_stored)
|
||||||
|
{
|
||||||
|
tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored;
|
||||||
|
ssb->blob_stored = node->next;
|
||||||
|
ssb->blob_stored_count--;
|
||||||
|
if (node->cleanup)
|
||||||
|
{
|
||||||
|
node->cleanup(ssb, node->user_data);
|
||||||
|
}
|
||||||
|
tf_free(node);
|
||||||
|
}
|
||||||
while (ssb->blob_want_added)
|
while (ssb->blob_want_added)
|
||||||
{
|
{
|
||||||
tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added;
|
tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added;
|
||||||
@@ -2784,6 +2806,16 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
|||||||
|
|
||||||
uv_run(ssb->loop, UV_RUN_NOWAIT);
|
uv_run(ssb->loop, UV_RUN_NOWAIT);
|
||||||
|
|
||||||
|
if (ssb->own_context)
|
||||||
|
{
|
||||||
|
if (!ssb->quiet)
|
||||||
|
{
|
||||||
|
tf_printf("closing ssb context\n");
|
||||||
|
}
|
||||||
|
JS_FreeContext(ssb->context);
|
||||||
|
JS_FreeRuntime(ssb->runtime);
|
||||||
|
ssb->own_context = false;
|
||||||
|
}
|
||||||
if (ssb->loop == &ssb->own_loop)
|
if (ssb->loop == &ssb->own_loop)
|
||||||
{
|
{
|
||||||
if (!ssb->quiet)
|
if (!ssb->quiet)
|
||||||
@@ -2800,16 +2832,6 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
|||||||
{
|
{
|
||||||
tf_printf("uv loop closed.\n");
|
tf_printf("uv loop closed.\n");
|
||||||
}
|
}
|
||||||
if (ssb->own_context)
|
|
||||||
{
|
|
||||||
if (!ssb->quiet)
|
|
||||||
{
|
|
||||||
tf_printf("closing ssb context\n");
|
|
||||||
}
|
|
||||||
JS_FreeContext(ssb->context);
|
|
||||||
JS_FreeRuntime(ssb->runtime);
|
|
||||||
ssb->own_context = false;
|
|
||||||
}
|
|
||||||
while (ssb->broadcasts)
|
while (ssb->broadcasts)
|
||||||
{
|
{
|
||||||
tf_ssb_broadcast_t* broadcast = ssb->broadcasts;
|
tf_ssb_broadcast_t* broadcast = ssb->broadcasts;
|
||||||
@@ -3961,9 +3983,53 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void (*cleanup)(tf_ssb_t* ssb, void* user_data), void* user_data)
|
||||||
|
{
|
||||||
|
tf_ssb_blob_stored_callback_node_t* node = tf_malloc(sizeof(tf_ssb_blob_stored_callback_node_t));
|
||||||
|
*node = (tf_ssb_blob_stored_callback_node_t) {
|
||||||
|
.callback = callback,
|
||||||
|
.cleanup = cleanup,
|
||||||
|
.user_data = user_data,
|
||||||
|
.next = ssb->blob_stored,
|
||||||
|
};
|
||||||
|
ssb->blob_stored = node;
|
||||||
|
ssb->blob_stored_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data)
|
||||||
|
{
|
||||||
|
tf_ssb_blob_stored_callback_node_t** it = &ssb->blob_stored;
|
||||||
|
while (*it)
|
||||||
|
{
|
||||||
|
if ((*it)->callback == callback && (*it)->user_data == user_data)
|
||||||
|
{
|
||||||
|
tf_ssb_blob_stored_callback_node_t* node = *it;
|
||||||
|
*it = node->next;
|
||||||
|
ssb->blob_stored_count--;
|
||||||
|
if (node->cleanup)
|
||||||
|
{
|
||||||
|
node->cleanup(ssb, node->user_data);
|
||||||
|
}
|
||||||
|
tf_free(node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
it = &(*it)->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id)
|
void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id)
|
||||||
{
|
{
|
||||||
|
tf_ssb_blob_stored_callback_node_t* next = NULL;
|
||||||
ssb->blobs_stored++;
|
ssb->blobs_stored++;
|
||||||
|
for (tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored; node; node = next)
|
||||||
|
{
|
||||||
|
next = node->next;
|
||||||
|
tf_trace_begin(ssb->trace, "blob stored callback");
|
||||||
|
node->callback(ssb, id, node->user_data);
|
||||||
|
tf_trace_end(ssb->trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_keys)
|
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_keys)
|
||||||
|
@@ -903,10 +903,6 @@ void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id)
|
|||||||
{
|
{
|
||||||
tf_printf("blob wants cache update failed: %s.\n", sqlite3_errmsg(db));
|
tf_printf("blob wants cache update failed: %s.\n", sqlite3_errmsg(db));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
tf_printf("want: %s\n", id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
}
|
}
|
||||||
|
@@ -384,28 +384,6 @@ void tf_ssb_ebt_set_send_clock_pending(tf_ssb_ebt_t* ebt, int pending)
|
|||||||
ebt->send_clock_pending = pending;
|
ebt->send_clock_pending = pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tf_ssb_ebt_debug_clock(tf_ssb_ebt_t* ebt, JSContext* context, JSValue debug)
|
|
||||||
{
|
|
||||||
uv_mutex_lock(&ebt->mutex);
|
|
||||||
for (int i = 0; i < ebt->entries_count; i++)
|
|
||||||
{
|
|
||||||
ebt_entry_t* entry = &ebt->entries[i];
|
|
||||||
JSValue clock = JS_NewObject(context);
|
|
||||||
JSValue out = JS_NewObject(context);
|
|
||||||
JSValue in = JS_NewObject(context);
|
|
||||||
JS_SetPropertyStr(context, out, "value", JS_NewInt64(context, entry->out));
|
|
||||||
JS_SetPropertyStr(context, out, "replicate", JS_NewBool(context, entry->out_replicate));
|
|
||||||
JS_SetPropertyStr(context, out, "receive", JS_NewBool(context, entry->out_receive));
|
|
||||||
JS_SetPropertyStr(context, clock, "out", out);
|
|
||||||
JS_SetPropertyStr(context, in, "value", JS_NewInt64(context, entry->in));
|
|
||||||
JS_SetPropertyStr(context, in, "replicate", JS_NewBool(context, entry->in_replicate));
|
|
||||||
JS_SetPropertyStr(context, in, "receive", JS_NewBool(context, entry->in_receive));
|
|
||||||
JS_SetPropertyStr(context, clock, "in", in);
|
|
||||||
JS_SetPropertyStr(context, debug, entry->id, clock);
|
|
||||||
}
|
|
||||||
uv_mutex_unlock(&ebt->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tf_ssb_ebt_get_progress(tf_ssb_ebt_t* ebt, int* in_pending, int* in_total, int* out_pending, int* out_total)
|
void tf_ssb_ebt_get_progress(tf_ssb_ebt_t* ebt, int* in_pending, int* in_total, int* out_pending, int* out_total)
|
||||||
{
|
{
|
||||||
uv_mutex_lock(&ebt->mutex);
|
uv_mutex_lock(&ebt->mutex);
|
||||||
|
@@ -106,15 +106,6 @@ int tf_ssb_ebt_get_send_clock_pending(tf_ssb_ebt_t* ebt);
|
|||||||
*/
|
*/
|
||||||
void tf_ssb_ebt_set_send_clock_pending(tf_ssb_ebt_t* ebt, int pending);
|
void tf_ssb_ebt_set_send_clock_pending(tf_ssb_ebt_t* ebt, int pending);
|
||||||
|
|
||||||
/**
|
|
||||||
** Get a JSON representation of the clock state for
|
|
||||||
** debugging.
|
|
||||||
** @param ebt The EBT instance.
|
|
||||||
** @param context The JS context.
|
|
||||||
** @param debug A JS object populated with the information.
|
|
||||||
*/
|
|
||||||
void tf_ssb_ebt_debug_clock(tf_ssb_ebt_t* ebt, JSContext* context, JSValue debug);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Get a representation of sync progress.
|
** Get a representation of sync progress.
|
||||||
** @param ebt The EBT instance.
|
** @param ebt The EBT instance.
|
||||||
|
25
src/ssb.h
25
src/ssb.h
@@ -680,6 +680,31 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
|
|||||||
*/
|
*/
|
||||||
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_with_keys);
|
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_with_keys);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** A callback called when a blob is added to the database.
|
||||||
|
** @param ssb The SSB instance.
|
||||||
|
** @param id The blob identifier.
|
||||||
|
** @param user_data The user data.
|
||||||
|
*/
|
||||||
|
typedef void(tf_ssb_blob_stored_callback_t)(tf_ssb_t* ssb, const char* id, void* user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Register a callback called when a blob is added to the database.
|
||||||
|
** @param ssb The SSB instance.
|
||||||
|
** @param callback The callback function.
|
||||||
|
** @param cleanup A function to call when the callback is removed.
|
||||||
|
** @param user_data User data to pass to the callback.
|
||||||
|
*/
|
||||||
|
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Remove a callback registered for when a blob is added to the database.
|
||||||
|
** @param ssb The SSB instance.
|
||||||
|
** @param callback The callback function.
|
||||||
|
** @param user_data User data registered with the callback.
|
||||||
|
*/
|
||||||
|
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Record that a new blob was stored.
|
** Record that a new blob was stored.
|
||||||
** @param ssb The SSB instance.
|
** @param ssb The SSB instance.
|
||||||
|
39
src/ssb.js.c
39
src/ssb.js.c
@@ -1082,7 +1082,7 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
uv_async_send(&sql_work->async);
|
uv_async_send(&sql_work->async);
|
||||||
sqlite3_stmt* statement = NULL;
|
sqlite3_stmt* statement = NULL;
|
||||||
sql_work->result = sqlite3_prepare_v2(db, sql_work->query, -1, &statement, NULL);
|
sql_work->result = sqlite3_prepare_v2(db, sql_work->query, -1, &statement, NULL);
|
||||||
if (sql_work->result == SQLITE_OK)
|
if (sql_work->result == SQLITE_OK && statement)
|
||||||
{
|
{
|
||||||
const uint8_t* p = sql_work->binds;
|
const uint8_t* p = sql_work->binds;
|
||||||
int column = 0;
|
int column = 0;
|
||||||
@@ -1162,7 +1162,11 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sql_work->result = r;
|
sql_work->result = r;
|
||||||
if (r != SQLITE_OK && r != SQLITE_DONE)
|
if (r == SQLITE_MISUSE)
|
||||||
|
{
|
||||||
|
sql_work->error = tf_strdup(sqlite3_errstr(sql_work->result));
|
||||||
|
}
|
||||||
|
else if (r != SQLITE_OK && r != SQLITE_DONE)
|
||||||
{
|
{
|
||||||
if (sqlite3_is_interrupted(db))
|
if (sqlite3_is_interrupted(db))
|
||||||
{
|
{
|
||||||
@@ -1176,10 +1180,15 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
_tf_ssb_sql_append(&sql_work->rows, &sql_work->rows_count, &(uint8_t[]) { 0 }, 1);
|
_tf_ssb_sql_append(&sql_work->rows, &sql_work->rows_count, &(uint8_t[]) { 0 }, 1);
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
}
|
}
|
||||||
else
|
else if (sql_work->result != SQLITE_OK)
|
||||||
{
|
{
|
||||||
sql_work->error = tf_strdup(sqlite3_errmsg(db));
|
sql_work->error = tf_strdup(sqlite3_errmsg(db));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sql_work->result = SQLITE_ERROR;
|
||||||
|
sql_work->error = tf_strdup("Statement not prepared");
|
||||||
|
}
|
||||||
uv_mutex_lock(&sql_work->lock);
|
uv_mutex_lock(&sql_work->lock);
|
||||||
sql_work->db = NULL;
|
sql_work->db = NULL;
|
||||||
uv_mutex_unlock(&sql_work->lock);
|
uv_mutex_unlock(&sql_work->lock);
|
||||||
@@ -1618,6 +1627,20 @@ static void _tf_ssb_on_message_added_callback(tf_ssb_t* ssb, const char* author,
|
|||||||
JS_FreeValue(context, string);
|
JS_FreeValue(context, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_on_blob_stored_callback(tf_ssb_t* ssb, const char* id, void* user_data)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue callback = JS_MKPTR(JS_TAG_OBJECT, user_data);
|
||||||
|
JSValue string = JS_NewString(context, id);
|
||||||
|
JSValue response = JS_Call(context, callback, JS_UNDEFINED, 1, &string);
|
||||||
|
if (tf_util_report_error(context, response))
|
||||||
|
{
|
||||||
|
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, user_data);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, response);
|
||||||
|
JS_FreeValue(context, string);
|
||||||
|
}
|
||||||
|
|
||||||
static void _tf_ssb_on_blob_want_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
|
static void _tf_ssb_on_blob_want_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
|
||||||
{
|
{
|
||||||
JSContext* context = tf_ssb_get_context(ssb);
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
@@ -1738,6 +1761,11 @@ static JSValue _tf_ssb_add_event_listener(JSContext* context, JSValueConst this_
|
|||||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||||
tf_ssb_add_message_added_callback(ssb, _tf_ssb_on_message_added_callback, _tf_ssb_cleanup_value, ptr);
|
tf_ssb_add_message_added_callback(ssb, _tf_ssb_on_message_added_callback, _tf_ssb_cleanup_value, ptr);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(event_name, "blob") == 0)
|
||||||
|
{
|
||||||
|
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||||
|
tf_ssb_add_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, _tf_ssb_cleanup_value, ptr);
|
||||||
|
}
|
||||||
else if (strcmp(event_name, "blob_want_added") == 0)
|
else if (strcmp(event_name, "blob_want_added") == 0)
|
||||||
{
|
{
|
||||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||||
@@ -1781,6 +1809,11 @@ static JSValue _tf_ssb_remove_event_listener(JSContext* context, JSValueConst th
|
|||||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||||
tf_ssb_remove_message_added_callback(ssb, _tf_ssb_on_message_added_callback, ptr);
|
tf_ssb_remove_message_added_callback(ssb, _tf_ssb_on_message_added_callback, ptr);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(event_name, "blob") == 0)
|
||||||
|
{
|
||||||
|
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||||
|
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, ptr);
|
||||||
|
}
|
||||||
else if (strcmp(event_name, "blob_want_added") == 0)
|
else if (strcmp(event_name, "blob_want_added") == 0)
|
||||||
{
|
{
|
||||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||||
|
@@ -1561,7 +1561,10 @@ static void _tf_ssb_rpc_delete_blobs_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
tf_free(ids);
|
tf_free(ids);
|
||||||
}
|
}
|
||||||
delete->duration_ms = (uv_hrtime() - start_ns) / 1000000LL;
|
delete->duration_ms = (uv_hrtime() - start_ns) / 1000000LL;
|
||||||
tf_printf("Deleted %d blobs in %d ms.\n", deleted, (int)delete->duration_ms);
|
if (deleted)
|
||||||
|
{
|
||||||
|
tf_printf("Deleted %d blobs in %d ms.\n", deleted, (int)delete->duration_ms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
@@ -1580,7 +1583,6 @@ static void _tf_ssb_rpc_start_delete_blobs_callback(tf_ssb_t* ssb, void* user_da
|
|||||||
|
|
||||||
static void _tf_ssb_rpc_start_delete_blobs(tf_ssb_t* ssb, int delay_ms)
|
static void _tf_ssb_rpc_start_delete_blobs(tf_ssb_t* ssb, int delay_ms)
|
||||||
{
|
{
|
||||||
tf_printf("will delete more blobs in %d ms\n", delay_ms);
|
|
||||||
tf_ssb_schedule_work(ssb, delay_ms, _tf_ssb_rpc_start_delete_blobs_callback, NULL);
|
tf_ssb_schedule_work(ssb, delay_ms, _tf_ssb_rpc_start_delete_blobs_callback, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1675,7 +1677,10 @@ static void _tf_ssb_rpc_delete_feeds_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
JS_FreeRuntime(runtime);
|
JS_FreeRuntime(runtime);
|
||||||
|
|
||||||
delete->duration_ms = (uv_hrtime() - start_ns) / 1000000LL;
|
delete->duration_ms = (uv_hrtime() - start_ns) / 1000000LL;
|
||||||
tf_printf("Deleted %d message in %d ms.\n", delete->deleted, (int)delete->duration_ms);
|
if (delete->deleted)
|
||||||
|
{
|
||||||
|
tf_printf("Deleted %d message in %d ms.\n", delete->deleted, (int)delete->duration_ms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _tf_ssb_rpc_delete_feeds_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
static void _tf_ssb_rpc_delete_feeds_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
@@ -1694,7 +1699,6 @@ static void _tf_ssb_rpc_start_delete_feeds_callback(tf_ssb_t* ssb, void* user_da
|
|||||||
|
|
||||||
static void _tf_ssb_rpc_start_delete_feeds(tf_ssb_t* ssb, int delay_ms)
|
static void _tf_ssb_rpc_start_delete_feeds(tf_ssb_t* ssb, int delay_ms)
|
||||||
{
|
{
|
||||||
tf_printf("will delete more feeds in %d ms\n", delay_ms);
|
|
||||||
tf_ssb_schedule_work(ssb, delay_ms, _tf_ssb_rpc_start_delete_feeds_callback, NULL);
|
tf_ssb_schedule_work(ssb, delay_ms, _tf_ssb_rpc_start_delete_feeds_callback, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -152,6 +152,12 @@ static void _wait_stored(tf_ssb_t* ssb, bool* stored)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _blob_stored(tf_ssb_t* ssb, const char* id, void* user_data)
|
||||||
|
{
|
||||||
|
tf_printf("blob stored %s\n", id);
|
||||||
|
*(bool*)user_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
void tf_ssb_test_ssb(const tf_test_options_t* options)
|
void tf_ssb_test_ssb(const tf_test_options_t* options)
|
||||||
{
|
{
|
||||||
tf_printf("Testing SSB.\n");
|
tf_printf("Testing SSB.\n");
|
||||||
@@ -224,8 +230,13 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
|
|||||||
|
|
||||||
char blob_id[k_id_base64_len] = { 0 };
|
char blob_id[k_id_base64_len] = { 0 };
|
||||||
const char* k_blob = "Hello, blob!";
|
const char* k_blob = "Hello, blob!";
|
||||||
|
bool blob_stored = false;
|
||||||
|
tf_ssb_add_blob_stored_callback(ssb0, _blob_stored, NULL, &blob_stored);
|
||||||
b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
|
b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
|
||||||
|
tf_ssb_notify_blob_stored(ssb0, blob_id);
|
||||||
|
tf_ssb_remove_blob_stored_callback(ssb0, _blob_stored, &blob_stored);
|
||||||
assert(b);
|
assert(b);
|
||||||
|
assert(blob_stored);
|
||||||
|
|
||||||
JSContext* context0 = tf_ssb_get_context(ssb0);
|
JSContext* context0 = tf_ssb_get_context(ssb0);
|
||||||
JSValue obj = JS_NewObject(context0);
|
JSValue obj = JS_NewObject(context0);
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
#define VERSION_NUMBER "0.0.32.1"
|
#define VERSION_NUMBER "0.2025.9-wip"
|
||||||
#define VERSION_NAME "This program kills fascists."
|
#define VERSION_NAME "This program kills fascists."
|
||||||
|
@@ -93,7 +93,7 @@ try:
|
|||||||
select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room'))
|
select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room'))
|
||||||
select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',))
|
select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',))
|
||||||
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||||
driver.switch_to.alert.accept()
|
wait.until(expected_conditions.alert_is_present()).accept()
|
||||||
|
|
||||||
select(driver, ['tf-navigation', 'shadow_root', '#identity'], ('click',))
|
select(driver, ['tf-navigation', 'shadow_root', '#identity'], ('click',))
|
||||||
select(driver, ['tf-navigation', 'shadow_root', '#logout'], ('click',))
|
select(driver, ['tf-navigation', 'shadow_root', '#logout'], ('click',))
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
VERSION=3.3.0
|
VERSION=3.3.1
|
||||||
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js -O deps/lit/lit-all.min.js
|
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js -O deps/lit/lit-all.min.js
|
||||||
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
|
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
|
||||||
cp -fv deps/lit/* apps/blog/
|
cp -fv deps/lit/* apps/blog/
|
||||||
|
Reference in New Issue
Block a user