Oasis 0.8.7 mobile (nodejs-project baseline)
BIN
src/client/assets/images/anarchy.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
src/client/assets/images/default-avatar.png
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
src/client/assets/images/default-market.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
src/client/assets/images/default-tribe.png
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
src/client/assets/images/democracy.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/client/assets/images/dictatorship.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
4
src/client/assets/images/favicon.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<title>oasis favicon</title>
|
||||
<text x="0" y="14">🏝️</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 139 B |
BIN
src/client/assets/images/karmatocracy.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
src/client/assets/images/majority.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
src/client/assets/images/minority.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/client/assets/images/snh-oasis.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
25
src/client/assets/images/world-map.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/client/assets/images/worldmap-z2.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
92
src/client/assets/larp/houses.json
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"academia": {
|
||||
"name": "ACADEMIA",
|
||||
"short": "aca",
|
||||
"motto": "Nosce te ipsum…",
|
||||
"roles": "Educators, teachers, coordinators",
|
||||
"function": "Education, understanding, talent discovery, selection process",
|
||||
"description": "All inhabitants start here. ACADEMIA receives newcomers, provides information on the simulation mechanics, presents each house's objectives and runs the selection process.",
|
||||
"month": 1,
|
||||
"image": "/assets/larp/images/academia.jpg"
|
||||
},
|
||||
"solaris": {
|
||||
"name": "SolarIS",
|
||||
"short": "sol",
|
||||
"motto": "The word makes us human. Violence, beasts.",
|
||||
"roles": "Rulers, lawyers, diplomats",
|
||||
"function": "Governance, law, dialogue, politics, communication",
|
||||
"description": "Holds the political voice. Mediates disputes, drafts norms, builds coalitions through dialogue and persuasion.",
|
||||
"month": 2,
|
||||
"image": "/assets/larp/images/solaris.jpg"
|
||||
},
|
||||
"arrakis": {
|
||||
"name": "ARRakis",
|
||||
"short": "arr",
|
||||
"motto": "How long do you say that it must be resolved?",
|
||||
"roles": "Engineers, scientists, technicians",
|
||||
"function": "Technology, invention, problem-solving, building and maintaining",
|
||||
"description": "The makers. They keep the machinery running, prototype, repair, automate; favour pragmatic solutions over endless debate.",
|
||||
"month": 3,
|
||||
"image": "/assets/larp/images/arrakis.jpg"
|
||||
},
|
||||
"terraverde": {
|
||||
"name": "TERRA.VErDE",
|
||||
"short": "ter",
|
||||
"motto": "Love nature, universe and being, because we are all one.",
|
||||
"roles": "Farmers, ecologists, doctors, nutritionists",
|
||||
"function": "Healthcare, food, nature, resources, climate management",
|
||||
"description": "Stewards of the living systems. Manage food, health and the relationship with the biosphere; long timescales, regenerative principles.",
|
||||
"month": 4,
|
||||
"image": "/assets/larp/images/terraverde.jpg"
|
||||
},
|
||||
"unsystem": {
|
||||
"name": "UNSYSTem",
|
||||
"short": "uns",
|
||||
"motto": "We do not make statements!",
|
||||
"roles": "Chaos agents, trolls, punks",
|
||||
"function": "Organization of disorder, tactical chaos",
|
||||
"description": "Holds the right to question, sabotage, provoke. Resists capture by any other house and keeps the system uncomfortable.",
|
||||
"month": 5,
|
||||
"image": "/assets/larp/images/unsystem.jpg"
|
||||
},
|
||||
"dogma": {
|
||||
"name": "DogmA",
|
||||
"short": "dog",
|
||||
"motto": "The question is not when to do it, the question is how.",
|
||||
"roles": "Thinkers, journalists, philosophers",
|
||||
"function": "Information control, knowledge, philosophy, memory, AI",
|
||||
"description": "The archive and the discourse. Curates knowledge, drafts narratives, governs how information is produced and propagated.",
|
||||
"month": 6,
|
||||
"image": "/assets/larp/images/dogma.jpg"
|
||||
},
|
||||
"helix": {
|
||||
"name": "HeliX",
|
||||
"short": "hlx",
|
||||
"motto": "If you do not smile, it's not my revolution.",
|
||||
"roles": "Clowns, influencers, musicians, priests",
|
||||
"function": "Entertainment, culture, humor, celebration, human relationships",
|
||||
"description": "Carries culture, ritual and joy. Holds the social fabric together through art, festival and shared emotion.",
|
||||
"month": 7,
|
||||
"image": "/assets/larp/images/helix.jpg"
|
||||
},
|
||||
"quark": {
|
||||
"name": "QuarK",
|
||||
"short": "quk",
|
||||
"motto": "Without security, there is no freedom!",
|
||||
"roles": "Athletes, soldiers, street people",
|
||||
"function": "Protection, security, defense, survival, family",
|
||||
"description": "The guard. Trains, defends, organises mutual aid; demands clear loyalties and clear lines.",
|
||||
"month": 8,
|
||||
"image": "/assets/larp/images/quark.jpg"
|
||||
},
|
||||
"hermandad": {
|
||||
"name": "HERmanDAD",
|
||||
"short": "hrm",
|
||||
"motto": "Grow. Build. Expand!",
|
||||
"roles": "Architects, builders, investors, industrialists",
|
||||
"function": "Construction, development, sustainability, logistics, production",
|
||||
"description": "The builders. Plan large works, organise production lines and supply chains; measure success in things shipped and capacity created.",
|
||||
"month": 9,
|
||||
"image": "/assets/larp/images/hermandad.jpg"
|
||||
}
|
||||
}
|
||||
BIN
src/client/assets/larp/images/academia.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/client/assets/larp/images/arrakis.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/client/assets/larp/images/dogma.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/client/assets/larp/images/helix.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/client/assets/larp/images/hermandad.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/client/assets/larp/images/quark.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/client/assets/larp/images/solaris.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/client/assets/larp/images/terraverde.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/client/assets/larp/images/unsystem.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
67
src/client/assets/styles/highlight.css
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: var(--base03);
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-regexp,
|
||||
.hljs-deletion {
|
||||
color: var(--base08);
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params,
|
||||
.hljs-meta,
|
||||
.hljs-link {
|
||||
color: var(--base09);
|
||||
}
|
||||
|
||||
.hljs-attribute {
|
||||
color: var(--base0A);
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-addition {
|
||||
color: var(--base0B);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: var(--base0D);
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: var(--base0E);
|
||||
}
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
color: var(--base05);
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-string, .hljs-link {
|
||||
background: #FFA500;
|
||||
user-select: text;
|
||||
}
|
||||
916
src/client/assets/styles/mobile.css
Normal file
|
|
@ -0,0 +1,916 @@
|
|||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 15px;
|
||||
overflow-x: hidden;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
img,
|
||||
video,
|
||||
canvas,
|
||||
iframe,
|
||||
table {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
pre,
|
||||
code {
|
||||
white-space: pre-wrap !important;
|
||||
word-break: break-word !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 4px !important;
|
||||
gap: 4px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 2px !important;
|
||||
gap: 4px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.top-bar-left,
|
||||
.top-bar-mid,
|
||||
.top-bar-right {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
align-items: stretch !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.top-bar-left nav ul,
|
||||
.top-bar-mid nav ul,
|
||||
.top-bar-right nav ul {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
gap: 6px !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.top-bar-left nav ul li,
|
||||
.top-bar-mid nav ul li,
|
||||
.top-bar-right nav ul li {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.top-bar-left nav ul li a,
|
||||
.top-bar-mid nav ul li a,
|
||||
.top-bar-right nav ul li a {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
padding: 6px 8px !important;
|
||||
font-size: 0.9rem !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.search-input,
|
||||
.feed-search-input,
|
||||
.activity-search-input {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
height: 40px !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
|
||||
.sidebar-left,
|
||||
.sidebar-right,
|
||||
.main-column {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
padding: 10px !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
.sidebar-left {
|
||||
order: 1 !important;
|
||||
}
|
||||
|
||||
.main-column {
|
||||
order: 3 !important;
|
||||
}
|
||||
|
||||
.sidebar-right {
|
||||
order: 2 !important;
|
||||
}
|
||||
|
||||
.sidebar-left nav ul,
|
||||
.sidebar-right nav ul {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.oasis-nav-header {
|
||||
font-size: 0.85rem !important;
|
||||
}
|
||||
|
||||
.oasis-nav-list li a {
|
||||
font-size: 0.9rem !important;
|
||||
padding: 8px 12px !important;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="submit"],
|
||||
input[type="button"],
|
||||
.filter-btn,
|
||||
.create-button,
|
||||
.edit-btn,
|
||||
.delete-btn,
|
||||
.join-btn,
|
||||
.leave-btn,
|
||||
.buy-btn {
|
||||
min-height: 44px !important;
|
||||
font-size: 16px !important;
|
||||
white-space: normal !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.feed-row,
|
||||
.comment-body-row,
|
||||
table {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
textarea,
|
||||
input:not([type="checkbox"]):not([type="radio"]),
|
||||
select {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:not(.oasis-nav-toggle):not(.oasis-nav-side-toggle),
|
||||
input[type="radio"] {
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
flex: none !important;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
footer,
|
||||
.footer {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: 12px !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
footer div {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
h1 { font-size: 1.35em !important; }
|
||||
h2 { font-size: 1.2em !important; }
|
||||
h3 { font-size: 1em !important; }
|
||||
|
||||
.small,
|
||||
.time {
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
.created-at {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
white-space: normal !important;
|
||||
word-break: break-word !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
line-height: 1.5 !important;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
.header-content .created-at {
|
||||
display: block !important;
|
||||
flex: 1 1 100% !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.post-meta,
|
||||
.feed-post-meta,
|
||||
.feed-row .small,
|
||||
.feed-row .time,
|
||||
.feed-row .created-at {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
white-space: normal !important;
|
||||
word-break: break-word !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
|
||||
.post-meta,
|
||||
.feed-post-meta {
|
||||
flex-direction: column !important;
|
||||
gap: 4px !important;
|
||||
}
|
||||
|
||||
.mode-buttons,
|
||||
.mode-buttons-cols,
|
||||
.mode-buttons-row,
|
||||
.filter-group,
|
||||
.activity-filter-grid,
|
||||
.activity-sub-filter,
|
||||
.filters .ui-toolbar,
|
||||
.filter-group,
|
||||
.inhabitant-action,
|
||||
.mode-buttons {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
gap: 6px !important;
|
||||
overflow-x: hidden !important;
|
||||
scroll-snap-type: none !important;
|
||||
grid-template-columns: none !important;
|
||||
}
|
||||
|
||||
.mode-buttons .column,
|
||||
.mode-buttons > div,
|
||||
.activity-filter-col {
|
||||
display: contents !important;
|
||||
}
|
||||
|
||||
.mode-buttons form,
|
||||
.filter-group form,
|
||||
.inhabitant-action form,
|
||||
.activity-filter-col form {
|
||||
width: auto !important;
|
||||
flex: 0 1 auto !important;
|
||||
scroll-snap-align: none !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.inhabitant-action .filter-btn {
|
||||
min-height: 0 !important;
|
||||
padding: 7px 12px !important;
|
||||
font-size: 13px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
.profile-sensors-box {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
gap: 6px !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.profile-sensors-box > * {
|
||||
max-width: 100% !important;
|
||||
word-break: break-word !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
}
|
||||
.inhabitant-activity-group {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.activity-filter-grid .filter-btn,
|
||||
.activity-filter-col .filter-btn,
|
||||
.activity-sub-filter .filter-btn {
|
||||
min-height: 0 !important;
|
||||
padding: 7px 12px !important;
|
||||
font-size: 13px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.mode-buttons .filter-btn,
|
||||
.mode-buttons button,
|
||||
.filter-group .filter-btn,
|
||||
.filter-group button,
|
||||
.activity-filter-grid .filter-btn,
|
||||
.activity-filter-grid button,
|
||||
.activity-sub-filter .filter-btn,
|
||||
.filters .filter-btn,
|
||||
.filters button {
|
||||
width: auto !important;
|
||||
flex: 0 0 auto !important;
|
||||
white-space: nowrap !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.inhabitant-card {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.inhabitant-left {
|
||||
width: 100% !important;
|
||||
text-align: center !important;
|
||||
flex: 0 0 auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.inhabitant-details {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.inhabitant-left > a:first-of-type,
|
||||
.inhabitant-left > a:first-child {
|
||||
display: inline-block !important;
|
||||
width: 150px !important;
|
||||
height: 150px !important;
|
||||
max-width: 150px !important;
|
||||
margin: 0 auto 12px !important;
|
||||
}
|
||||
|
||||
.inhabitant-photo,
|
||||
.inhabitant-photo-details {
|
||||
width: 150px !important;
|
||||
height: 150px !important;
|
||||
max-width: 150px !important;
|
||||
object-fit: cover !important;
|
||||
border-radius: 50% !important;
|
||||
display: block !important;
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
.inhabitants-list,
|
||||
.inhabitants-grid {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 10px !important;
|
||||
}
|
||||
|
||||
.tribe-card {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.tribe-grid {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 10px !important;
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.tribes-list,
|
||||
.tribes-grid {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 10px !important;
|
||||
}
|
||||
|
||||
.tribe-card-image {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.tribe-section-nav {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
overflow-x: visible !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.tribe-section-group {
|
||||
display: contents !important;
|
||||
}
|
||||
|
||||
.tribe-section-btn {
|
||||
font-size: 12px !important;
|
||||
padding: 7px 12px !important;
|
||||
white-space: nowrap !important;
|
||||
flex: 0 0 auto !important;
|
||||
}
|
||||
|
||||
.tribe-details {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.tribe-side {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.tribe-main {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.tribe-content-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.tribe-inhabitants-grid {
|
||||
grid-template-columns: repeat(2, 1fr) !important;
|
||||
}
|
||||
|
||||
.tribe-media-grid {
|
||||
grid-template-columns: repeat(2, 1fr) !important;
|
||||
}
|
||||
|
||||
.tribe-overview-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.tribe-mode-buttons {
|
||||
flex-wrap: wrap !important;
|
||||
}
|
||||
|
||||
.tribe-card-hero-image {
|
||||
height: 180px !important;
|
||||
}
|
||||
|
||||
.tribe-votation-option {
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.tribe-content-header {
|
||||
flex-direction: column !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.tribe-content-filters {
|
||||
flex-wrap: wrap !important;
|
||||
}
|
||||
|
||||
.tribe-media-filters {
|
||||
flex-wrap: wrap !important;
|
||||
}
|
||||
|
||||
.forum-card {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.forum-score-col,
|
||||
.forum-main-col,
|
||||
.root-vote-col {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.forum-header-row {
|
||||
flex-direction: column !important;
|
||||
gap: 4px !important;
|
||||
}
|
||||
|
||||
.forum-meta {
|
||||
flex-wrap: wrap !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
|
||||
.forum-thread-header {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.forum-comment {
|
||||
margin-left: 0 !important;
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
|
||||
.comment-body-row {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.comment-vote-col,
|
||||
.comment-text-col {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.forum-score-box,
|
||||
.forum-score-form {
|
||||
flex-direction: row !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.new-message-form textarea,
|
||||
.comment-textarea {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
[style*="grid-template-columns: repeat(6"] {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
[style*="grid-template-columns: repeat(3"] {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
[style*="grid-template-columns:repeat(6"] {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
[style*="grid-template-columns:repeat(3"] {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
[style*="grid-template-columns: repeat(auto-fit"] {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
[style*="grid-template-columns:repeat(auto-fit"] {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
[style*="width:50%"] {
|
||||
width: 100% !important;
|
||||
}
|
||||
.inhabitant-karma-ubi .ubi-line{display:block;font-size:0.9em}
|
||||
.pad-editor-container{flex-direction:column !important}
|
||||
.pad-editor-area,.pad-members-list{width:100% !important;min-width:unset !important}
|
||||
.chat-main-split{flex-direction:column !important}
|
||||
.chat-messages-column,.chat-participants-column{width:100% !important;min-width:unset !important;flex:unset !important}
|
||||
.games-grid{grid-template-columns:1fr !important}
|
||||
.game-card{width:100% !important}
|
||||
|
||||
.main-column [class*="-row"],
|
||||
.main-column [class*="-card"],
|
||||
.main-column [class*="-item"],
|
||||
.main-column [class*="-entry"],
|
||||
.main-column .card,
|
||||
.main-column article {
|
||||
flex-wrap: wrap !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
.main-column [class*="-row"] > *,
|
||||
.main-column [class*="-card"] > * {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
button:not([class]),
|
||||
input[type="submit"]:not([class]),
|
||||
input[type="button"]:not([class]),
|
||||
.filter-btn,
|
||||
a.filter-btn,
|
||||
.conn-actions button,
|
||||
.peers-conn-actions button {
|
||||
background-color: #FFA500 !important;
|
||||
color: #1a1a1a !important;
|
||||
border: 1px solid #FFA500 !important;
|
||||
border-radius: 8px !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 14px !important;
|
||||
min-height: 42px !important;
|
||||
padding: 9px 14px !important;
|
||||
text-align: center !important;
|
||||
white-space: normal !important;
|
||||
max-width: 100% !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
}
|
||||
.filter-btn.active {
|
||||
background-color: #FFD700 !important;
|
||||
border-color: #FFD700 !important;
|
||||
}
|
||||
|
||||
.audios-search,
|
||||
.videos-search,
|
||||
.images-search {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin: 8px 0 !important;
|
||||
}
|
||||
.filter-box {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
gap: 8px !important;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
.filter-box__input {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
height: 42px !important;
|
||||
font-size: 16px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
.filter-box__controls {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
width: 100% !important;
|
||||
gap: 8px !important;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
.filter-box__controls .transfer-range {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex: 1 1 100% !important;
|
||||
width: 100% !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
.filter-box__number {
|
||||
flex: 1 1 0 !important;
|
||||
min-width: 0 !important;
|
||||
width: auto !important;
|
||||
height: 42px !important;
|
||||
font-size: 15px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
.filter-box__select {
|
||||
flex: 1 1 auto !important;
|
||||
min-width: 0 !important;
|
||||
height: 42px !important;
|
||||
font-size: 15px !important;
|
||||
}
|
||||
.filter-box__button {
|
||||
flex: 0 0 auto !important;
|
||||
width: auto !important;
|
||||
min-height: 42px !important;
|
||||
white-space: nowrap !important;
|
||||
background-color: #FFA500 !important;
|
||||
color: #1a1a1a !important;
|
||||
border: 1px solid #FFA500 !important;
|
||||
border-radius: 8px !important;
|
||||
font-weight: 600 !important;
|
||||
padding: 9px 16px !important;
|
||||
}
|
||||
.filters .ui-toolbar--filters {
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.main-column button,
|
||||
.main-column input,
|
||||
.main-column select,
|
||||
.main-column textarea,
|
||||
.main-column .btn,
|
||||
.main-column .create-button,
|
||||
.main-column .update-btn,
|
||||
.main-column .delete-btn,
|
||||
.main-column .edit-btn,
|
||||
.main-column .join-btn,
|
||||
.main-column .leave-btn,
|
||||
.main-column .buy-btn,
|
||||
.main-column .submit-button {
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
}
|
||||
.main-column button,
|
||||
.main-column .btn,
|
||||
.main-column .create-button,
|
||||
.main-column .update-btn,
|
||||
.main-column .delete-btn,
|
||||
.main-column .submit-button {
|
||||
white-space: normal !important;
|
||||
}
|
||||
.main-column form {
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.card-field {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
gap: 4px !important;
|
||||
}
|
||||
.card-field .card-value,
|
||||
.card-field .card-label {
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
white-space: normal !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
word-break: break-word !important;
|
||||
}
|
||||
.card-value .user-link,
|
||||
.card-value a {
|
||||
white-space: normal !important;
|
||||
max-width: 100% !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
}
|
||||
|
||||
.market-grid,
|
||||
.shop-products-grid,
|
||||
.jobs-grid {
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 12px !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.market-card,
|
||||
.market-item {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.cycle-info {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
||||
gap: 8px !important;
|
||||
}
|
||||
.kpi {
|
||||
min-width: 0 !important;
|
||||
padding: 8px !important;
|
||||
}
|
||||
.kpi__label {
|
||||
letter-spacing: 0 !important;
|
||||
word-break: normal !important;
|
||||
overflow-wrap: break-word !important;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
.kpi__value {
|
||||
word-break: break-word !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.table-wrap table,
|
||||
.table-wrap .table {
|
||||
display: table !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
table-layout: fixed !important;
|
||||
}
|
||||
.table-wrap th,
|
||||
.table-wrap td {
|
||||
white-space: normal !important;
|
||||
word-break: normal !important;
|
||||
overflow-wrap: break-word !important;
|
||||
font-size: 10px !important;
|
||||
padding: 4px 3px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.search-filters-row {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.search-filters-table,
|
||||
.search-filters-table tbody,
|
||||
.search-filters-table tr,
|
||||
.search-filters-table td {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.search-filters-table td {
|
||||
padding: 4px 0 !important;
|
||||
}
|
||||
.search-filters-table input,
|
||||
.search-filters-table select,
|
||||
.search-oasis-id {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.oasis-id-copy {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
font-family: monospace !important;
|
||||
font-size: 12px !important;
|
||||
text-align: center !important;
|
||||
padding: 8px !important;
|
||||
border: 1px solid #FFA500 !important;
|
||||
border-radius: 8px !important;
|
||||
background: rgba(255, 165, 0, 0.08) !important;
|
||||
color: inherit !important;
|
||||
-webkit-user-select: all !important;
|
||||
user-select: all !important;
|
||||
}
|
||||
.peer-key,
|
||||
.user-link.peer-key {
|
||||
-webkit-user-select: all !important;
|
||||
user-select: all !important;
|
||||
word-break: break-all !important;
|
||||
}
|
||||
|
||||
.create-button,
|
||||
button.create-button,
|
||||
a.create-button {
|
||||
background-color: #ffcc00 !important;
|
||||
border-color: #e6b800 !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.peers-list .block-info-table,
|
||||
.peers-technical-block .block-info-table,
|
||||
.gov-overview {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
border: 0 !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.peers-list .block-info-table tbody,
|
||||
.peers-technical-block .block-info-table tbody,
|
||||
.gov-overview tbody {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.gov-overview thead { display: none !important; }
|
||||
.peers-list .block-info-table tr:first-child,
|
||||
.peers-technical-block .block-info-table tr:first-child {
|
||||
display: none !important;
|
||||
}
|
||||
.peers-list .block-info-table tr,
|
||||
.peers-technical-block .block-info-table tr,
|
||||
.gov-overview tbody tr {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid rgba(255, 165, 0, 0.35) !important;
|
||||
border-radius: 8px !important;
|
||||
margin: 0 0 8px 0 !important;
|
||||
padding: 6px 10px !important;
|
||||
background: rgba(255, 165, 0, 0.04) !important;
|
||||
}
|
||||
.peers-list .block-info-table td,
|
||||
.peers-technical-block .block-info-table td,
|
||||
.gov-overview tbody td {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: baseline !important;
|
||||
gap: 10px !important;
|
||||
width: 100% !important;
|
||||
border: 0 !important;
|
||||
padding: 3px 0 !important;
|
||||
font-size: 12px !important;
|
||||
text-align: right !important;
|
||||
word-break: break-all !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
}
|
||||
.peers-list .block-info-table td::before,
|
||||
.peers-technical-block .block-info-table td::before,
|
||||
.gov-overview tbody td::before {
|
||||
content: attr(data-label);
|
||||
flex: 0 0 auto !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px !important;
|
||||
letter-spacing: .03em;
|
||||
opacity: .7 !important;
|
||||
text-align: left !important;
|
||||
white-space: nowrap !important;
|
||||
word-break: normal !important;
|
||||
}
|
||||
.peers-technical-block .block-info-table td[data-label=""] { justify-content: center !important; }
|
||||
.peers-technical-block .block-info-table td[data-label=""]::before { content: none !important; }
|
||||
.peers-list .block-info-table td .peer-key,
|
||||
.peers-technical-block .block-info-table td .peer-key { text-align: right !important; }
|
||||
6441
src/client/assets/styles/style.css
Normal file
1005
src/client/assets/themes/OasisMobile.css
Normal file
15
src/client/assets/translations/i18n.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const path = require('path');
|
||||
let i18n = {};
|
||||
const languages = ['en', 'es', 'fr', 'eu', 'de', 'it', 'pt', 'zh', 'ar', 'hi', 'ru'];
|
||||
|
||||
languages.forEach(language => {
|
||||
try {
|
||||
const languagePath = path.join(__dirname, `oasis_${language}.js`);
|
||||
const languageData = require(languagePath);
|
||||
i18n[language] = languageData[language];
|
||||
} catch (error) {
|
||||
console.error(`Failed to load language file for ${language}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = i18n;
|
||||
3872
src/client/assets/translations/oasis_ar.js
Normal file
3868
src/client/assets/translations/oasis_de.js
Normal file
3873
src/client/assets/translations/oasis_en.js
Normal file
3873
src/client/assets/translations/oasis_es.js
Normal file
3873
src/client/assets/translations/oasis_eu.js
Normal file
3872
src/client/assets/translations/oasis_fr.js
Normal file
3872
src/client/assets/translations/oasis_hi.js
Normal file
3873
src/client/assets/translations/oasis_it.js
Normal file
3873
src/client/assets/translations/oasis_pt.js
Normal file
3872
src/client/assets/translations/oasis_ru.js
Normal file
3873
src/client/assets/translations/oasis_zh.js
Normal file
10
src/client/cli-cmd-aliases.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
feed: 'createFeedStream',
|
||||
history: 'createHistoryStream',
|
||||
hist: 'createHistoryStream',
|
||||
public: 'getPublicKey',
|
||||
pub: 'getPublicKey',
|
||||
log: 'createLogStream',
|
||||
logt: 'messagesByType',
|
||||
conf: 'config',
|
||||
};
|
||||
124
src/client/gui.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const debug = require('../server/node_modules/debug')('oasis');
|
||||
const lodash = require('../server/node_modules/lodash');
|
||||
const ssbClient = require('../server/node_modules/ssb-client');
|
||||
const ssbConfig = require('../server/node_modules/ssb-config');
|
||||
const ssbKeys = require('../server/node_modules/ssb-keys');
|
||||
const { printMetadata } = require('../server/ssb_metadata');
|
||||
const updateFlagPath = path.join(__dirname, "../server/.update_required");
|
||||
|
||||
let internalSSB = null;
|
||||
try {
|
||||
const { server } = require('../server/SSB_server');
|
||||
internalSSB = server;
|
||||
} catch {}
|
||||
|
||||
if (process.env.OASIS_TEST) {
|
||||
ssbConfig.path = fs.mkdtempSync(path.join(os.tmpdir(), "oasis-"));
|
||||
ssbConfig.keys = ssbKeys.generate();
|
||||
}
|
||||
|
||||
const socketPath = path.join(ssbConfig.path, "socket");
|
||||
const publicInteger = ssbConfig.keys.public.replace(".ed25519", "");
|
||||
const remote = `unix:${socketPath}~noauth:${publicInteger}`;
|
||||
|
||||
const connect = (options) =>
|
||||
new Promise((resolve, reject) => {
|
||||
ssbClient(process.env.OASIS_TEST ? ssbConfig.keys : null, options)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
|
||||
let closing = false;
|
||||
let clientHandle;
|
||||
|
||||
const attemptConnectionWithBackoff = (attempt = 1) => {
|
||||
const maxAttempts = 5;
|
||||
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
connect({ remote })
|
||||
.then(resolve)
|
||||
.catch((error) => {
|
||||
if (attempt >= maxAttempts) {
|
||||
return reject(new Error("Failed to connect after multiple attempts"));
|
||||
}
|
||||
setTimeout(() => {
|
||||
attemptConnectionWithBackoff(attempt + 1).then(resolve).catch(reject);
|
||||
}, delay);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let pendingConnection = null;
|
||||
|
||||
const ensureConnection = (customConfig) => {
|
||||
if (pendingConnection === null) {
|
||||
pendingConnection = new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
attemptConnectionWithBackoff()
|
||||
.then(resolve)
|
||||
.catch(() => {
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const cancel = () => (pendingConnection = null);
|
||||
pendingConnection.then(cancel, cancel);
|
||||
}
|
||||
|
||||
return pendingConnection;
|
||||
};
|
||||
|
||||
module.exports = ({ offline, port = 3000, host = 'localhost', isPublic = false }) => {
|
||||
const customConfig = JSON.parse(JSON.stringify(ssbConfig));
|
||||
if (offline === true) {
|
||||
lodash.set(customConfig, "conn.autostart", false);
|
||||
}
|
||||
lodash.set(
|
||||
customConfig,
|
||||
"conn.hops",
|
||||
lodash.get(ssbConfig, "conn.hops", lodash.get(ssbConfig.friends, "hops", 0))
|
||||
);
|
||||
|
||||
const cooler = {
|
||||
open() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (internalSSB) {
|
||||
const { printMetadata, colors } = require('../server/ssb_metadata');
|
||||
printMetadata('OASIS GUI', colors.yellow, port, host, offline, isPublic);
|
||||
return resolve(internalSSB);
|
||||
}
|
||||
|
||||
if (clientHandle && clientHandle.closed === false) {
|
||||
return resolve(clientHandle);
|
||||
}
|
||||
|
||||
ensureConnection(customConfig).then((ssb) => {
|
||||
if (!ssb) return reject(new Error("No SSB server available"));
|
||||
clientHandle = ssb;
|
||||
if (closing) {
|
||||
cooler.close();
|
||||
reject(new Error("Closing Oasis"));
|
||||
} else {
|
||||
const { printMetadata, colors } = require('../server/ssb_metadata');
|
||||
printMetadata('OASIS GUI', colors.yellow, port, host, offline, isPublic);
|
||||
resolve(ssb);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
closing = true;
|
||||
if (clientHandle && clientHandle.closed === false) {
|
||||
clientHandle.close();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return cooler;
|
||||
};
|
||||
210
src/client/middleware.js
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
const path = require("path");
|
||||
const os = require("os");
|
||||
const Koa = require(path.join(__dirname, "../server/node_modules/koa"));
|
||||
const koaStatic = require(path.join(__dirname, "../server/node_modules/koa-static"));
|
||||
const { join } = require("path");
|
||||
const mount = require(path.join(__dirname, "../server/node_modules/koa-mount"));
|
||||
|
||||
function obfuscateClearnetHtml(html) {
|
||||
if (typeof html !== 'string' || html.length === 0) return html;
|
||||
const preserve = [];
|
||||
const stash = (re) => {
|
||||
html = html.replace(re, (m) => {
|
||||
preserve.push(m);
|
||||
return `${preserve.length - 1}`;
|
||||
});
|
||||
};
|
||||
stash(/<pre[\s\S]*?<\/pre>/gi);
|
||||
stash(/<textarea[\s\S]*?<\/textarea>/gi);
|
||||
stash(/<style[\s\S]*?<\/style>/gi);
|
||||
html = html.replace(/<!--[\s\S]*?-->/g, '');
|
||||
html = html.replace(/>[\s\n\r\t]+</g, '><');
|
||||
html = html.replace(/[ \t]{2,}/g, ' ');
|
||||
html = html.replace(/[\r\n]+/g, '');
|
||||
html = html.replace(/(\d+)/g, (_, i) => preserve[Number(i)] || '');
|
||||
return html;
|
||||
}
|
||||
|
||||
const collectLocalIPs = () => {
|
||||
const out = [];
|
||||
try {
|
||||
const ifaces = os.networkInterfaces();
|
||||
for (const name of Object.keys(ifaces)) {
|
||||
for (const info of (ifaces[name] || [])) {
|
||||
if (info && !info.internal && (info.family === 'IPv4' || info.family === 4)) {
|
||||
out.push(info.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
return out;
|
||||
};
|
||||
|
||||
module.exports = ({ host, port, middleware, allowHost }) => {
|
||||
const assets = new Koa()
|
||||
assets.use(koaStatic(join(__dirname, "..", "client", "assets")));
|
||||
|
||||
const app = new Koa();
|
||||
const validHosts = [];
|
||||
|
||||
const isClearnetPath = (request) => {
|
||||
const url = String(request.url || '');
|
||||
return url === '/c' || url.startsWith('/c/') || url.startsWith('/c?');
|
||||
};
|
||||
|
||||
const isValidRequest = (request) => {
|
||||
if (isClearnetPath(request)) return request.method === 'GET';
|
||||
if (validHosts.includes(request.hostname) !== true) {
|
||||
return false;
|
||||
}
|
||||
if (request.method !== "GET") {
|
||||
if (request.header.referer == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const refererUrl = new URL(request.header.referer);
|
||||
if (validHosts.includes(refererUrl.hostname) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (refererUrl.pathname.startsWith("/blob/")) {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
app.on("error", (err, ctx) => {
|
||||
if (err && (err.code === 'ECONNRESET' || err.code === 'EPIPE')) {
|
||||
return;
|
||||
}
|
||||
if (err && (err.name === 'BadRequestError' || err.status === 400)) {
|
||||
console.error(`[400] ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
console.error(err);
|
||||
if (ctx && isValidRequest(ctx.request)) {
|
||||
err.message = err.message || 'Internal server error';
|
||||
err.expose = true;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
app.use(mount("/assets", assets));
|
||||
|
||||
const maptiles = new Koa();
|
||||
maptiles.use(koaStatic(join(__dirname, "..", "maps", "tiles")));
|
||||
app.use(mount("/maptiles", maptiles));
|
||||
|
||||
const mapcache = new Koa();
|
||||
mapcache.use(koaStatic(join(__dirname, "..", "maps", "cache")));
|
||||
app.use(mount("/mapcache", mapcache));
|
||||
|
||||
|
||||
const gamesStatic = new Koa();
|
||||
gamesStatic.use(koaStatic(join(__dirname, "..", "games")));
|
||||
app.use(mount("/game-assets", gamesStatic));
|
||||
|
||||
app.use(mount("/js", koaStatic(path.join(__dirname, 'public/js'))));
|
||||
app.use(koaStatic(path.join(__dirname, 'public')));
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
|
||||
//console.log("Requesting:", ctx.path); // uncomment to check for HTTP requests
|
||||
|
||||
const isClearnet = isClearnetPath(ctx.request);
|
||||
const csp = isClearnet
|
||||
? [
|
||||
"default-src 'self'",
|
||||
"script-src 'none'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data:",
|
||||
"media-src 'self' blob:",
|
||||
"connect-src 'self'",
|
||||
"form-action 'self'",
|
||||
"object-src 'none'",
|
||||
"base-uri 'none'",
|
||||
"frame-ancestors 'none'"
|
||||
].join("; ")
|
||||
: [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' http://localhost:3000/js",
|
||||
"style-src 'self'",
|
||||
"img-src 'self'",
|
||||
"media-src 'self' blob:",
|
||||
"worker-src 'self' blob:",
|
||||
"frame-src 'self'",
|
||||
"form-action 'self'",
|
||||
"object-src 'none'",
|
||||
"base-uri 'none'",
|
||||
"frame-ancestors 'none'"
|
||||
].join("; ");
|
||||
|
||||
ctx.set("Content-Security-Policy", csp);
|
||||
ctx.set("X-Frame-Options", "SAMEORIGIN");
|
||||
|
||||
ctx.set("X-Content-Type-Options", "nosniff");
|
||||
|
||||
ctx.set("Referrer-Policy", "same-origin");
|
||||
ctx.set("Permissions-Policy", "speaker=(self)");
|
||||
|
||||
const validHostsString = validHosts.join(" or ");
|
||||
|
||||
ctx.assert(
|
||||
isValidRequest(ctx.request),
|
||||
400,
|
||||
`Request must be addressed to ${validHostsString} and non-GET requests must contain non-blob referer.`
|
||||
);
|
||||
|
||||
await next();
|
||||
|
||||
if (isClearnet && typeof ctx.body === 'string') {
|
||||
const type = String(ctx.response.type || ctx.response.get('Content-Type') || '').toLowerCase();
|
||||
if (type.includes('html')) {
|
||||
ctx.body = obfuscateClearnetHtml(ctx.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// pdf viewer
|
||||
const pdfjsPath = path.join(__dirname, '../server/node_modules/pdfjs-dist/build/pdf.min.js');
|
||||
app.use(koaStatic(pdfjsPath));
|
||||
|
||||
middleware.forEach((m) => app.use(m));
|
||||
|
||||
const server = app.listen({ host, port });
|
||||
|
||||
server.on("listening", () => {
|
||||
const address = server.address();
|
||||
|
||||
if (typeof address === "string") {
|
||||
throw new Error("HTTP server should never bind to Unix socket");
|
||||
}
|
||||
|
||||
if (allowHost !== null) {
|
||||
validHosts.push(allowHost);
|
||||
}
|
||||
|
||||
validHosts.push(address.address);
|
||||
|
||||
if (validHosts.includes(host) === false) {
|
||||
validHosts.push(host);
|
||||
}
|
||||
|
||||
for (const ip of collectLocalIPs()) {
|
||||
if (validHosts.includes(ip) === false) validHosts.push(ip);
|
||||
}
|
||||
|
||||
for (const loopback of ['localhost', '127.0.0.1']) {
|
||||
if (validHosts.includes(loopback) === false) validHosts.push(loopback);
|
||||
}
|
||||
});
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
70
src/client/oasis_client.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"use strict";
|
||||
|
||||
const path = require('path');
|
||||
const yargs = require(path.join(__dirname, '../server/node_modules/yargs'));
|
||||
const { hideBin } = require(path.join(__dirname, '../server/node_modules/yargs/helpers'));
|
||||
const _ = require(path.join(__dirname, '../server/node_modules/lodash'));
|
||||
|
||||
const moduleAlias = require(path.join(__dirname, '../server/node_modules/module-alias'));
|
||||
moduleAlias.addAlias('punycode', 'punycode/');
|
||||
|
||||
const HELP_TEXT = `Usage: sh oasis.sh [mode] [options]
|
||||
|
||||
Modes:
|
||||
gui Launch the Oasis web GUI (default if no mode given).
|
||||
server, pub Launch only the Oasis Sbot (headless, PUB mode).
|
||||
help, -h, --help Show this help message.
|
||||
|
||||
PUB admin commands (require the Oasis Sbot to be running):
|
||||
whoami Print this Oasis ID.
|
||||
invite [N] Create an invite code. N = number of uses (default 1).
|
||||
name <text> Set this Oasis display name.
|
||||
announce <host> [port] Publish a pub address (default port 8008).
|
||||
follow <feedId> Follow another Oasis ID / feed.
|
||||
status Show peer / replication status.
|
||||
gossip List known gossip peers.
|
||||
|
||||
GUI options (forwarded to the backend):
|
||||
--host=<ip> Hostname / IP the web UI listens on (default: localhost).
|
||||
Use 0.0.0.0 to expose on a VPS.
|
||||
--port=<n> Port for the web UI (default: 3000).
|
||||
--allow-host=<host> Extra hostname allowed when behind a reverse proxy.
|
||||
--public Public-hosting mode: disables POST and redacts content
|
||||
from people who haven't opted in to public hosting.
|
||||
--offline Don't connect to Oasis peers or pubs.
|
||||
--no-open Don't auto-open a browser tab on launch (useful on VPS).
|
||||
--debug Verbose logging.
|
||||
|
||||
Examples:
|
||||
sh oasis.sh
|
||||
sh oasis.sh server
|
||||
sh oasis.sh invite 100
|
||||
sh oasis.sh name "My PUB"
|
||||
sh oasis.sh announce mypub.example.com
|
||||
sh oasis.sh --host=0.0.0.0 --port=8080 --no-open
|
||||
`;
|
||||
|
||||
const cli = (presets /*, defaultConfigFile */) => {
|
||||
const argv = process.argv.slice(2);
|
||||
if (argv.includes('-h') || argv.includes('--help') || argv.includes('-help') || argv.includes('help')) {
|
||||
process.stdout.write(HELP_TEXT);
|
||||
process.exit(0);
|
||||
}
|
||||
return yargs(hideBin(process.argv))
|
||||
.scriptName("oasis")
|
||||
.env("OASIS")
|
||||
.help(false)
|
||||
.version(false)
|
||||
.options("open", { default: _.get(presets, "open", true), type: "boolean" })
|
||||
.options("offline", { default: _.get(presets, "offline", false), type: "boolean" })
|
||||
.options("host", { default: _.get(presets, "host", "localhost"), type: "string" })
|
||||
.options("allow-host", { default: _.get(presets, "allow-host", null), type: "string" })
|
||||
.options("port", { default: _.get(presets, "port", 3000), type: "number" })
|
||||
.options("public", { default: _.get(presets, "public", false), type: "boolean" })
|
||||
.options("debug", { default: _.get(presets, "debug", false), type: "boolean" })
|
||||
.parserConfiguration({ "strip-aliased": true })
|
||||
.argv;
|
||||
};
|
||||
|
||||
module.exports = { cli };
|
||||
|
||||
115
src/client/public/docs/ecoin.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
|
||||
ECOin - Copyright (c) - 2014/2025 - GPLv3 - epsylon@riseup.net (https://ecoin.03c8.net)
|
||||
|
||||
===========================================
|
||||
# SOURCES for P2P Crypto-Currency (ECOin) #
|
||||
===========================================
|
||||
|
||||
Testing machine is: Debian GNU/Linux 12 (bookworm) (x86_64).
|
||||
|
||||
All of the commands should be executed in a shell.
|
||||
|
||||
------------------------------
|
||||
|
||||
(0.) Clone the github tree to get the source code:
|
||||
|
||||
+ Official:
|
||||
|
||||
git clone http://code.03c8.net/epsylon/ecoin
|
||||
|
||||
+ Mirror:
|
||||
|
||||
git clone https://github.com/epsylon/ecoin
|
||||
|
||||
------------------------------
|
||||
|
||||
===================================================
|
||||
# SERVER -ecoind- for P2P Crypto-Currency (ECOin) #
|
||||
===================================================
|
||||
|
||||
(0.) Version libraries:
|
||||
|
||||
- Libboost -> source code 1.68 provided at: src/boost_1_68_0
|
||||
|
||||
(1.) Install dependencies:
|
||||
|
||||
sudo apt-get install build-essential libssl-dev libssl3 libdb5.3-dev libdb5.3++-dev libleveldb-dev miniupnpc libminiupnpc-dev
|
||||
|
||||
+ Optionally install qrencode (and set USE_QRCODE=1):
|
||||
|
||||
sudo apt-get install libqrencode-dev
|
||||
|
||||
(2.) Now you should be able to build ecoind:
|
||||
|
||||
cd src/
|
||||
make -f makefile.linux USE_UPNP=- USE_IPV6=-
|
||||
strip ecoind
|
||||
|
||||
An executable named 'ecoind' will be built.
|
||||
|
||||
Now you can launch: ./ecoind to run your ECOin server.
|
||||
|
||||
------------------------------
|
||||
|
||||
============================================
|
||||
# WALLET for P2P Crypto-Currency (ECOin) #
|
||||
============================================
|
||||
|
||||
(0.) Version libraries:
|
||||
|
||||
- Libboost -> source code 1.68 provided at: src/boost_1_68_0
|
||||
|
||||
(1.) First, make sure that the required packages for Qt5 development (an the others required for building the daemon) are installed:
|
||||
|
||||
sudo apt-get install qt5-qmake qtbase5-dev build-essential libssl-dev libssl3 libdb5.3-dev libdb5.3++-dev libleveldb-dev miniupnpc libminiupnpc-dev
|
||||
|
||||
+ Optionally install qrencode (and set USE_QRCODE=1):
|
||||
|
||||
sudo apt-get install libqrencode-dev
|
||||
|
||||
(2.) Then execute the following:
|
||||
|
||||
qmake USE_UPNP=- USE_IPV6=-
|
||||
make
|
||||
|
||||
An executable named 'ecoin-qt' will be built.
|
||||
|
||||
Now you can launch: ./ecoin-qt to run your ECOin wallet/GUI.
|
||||
|
||||
------------------------------
|
||||
========================================
|
||||
+ CPU MINER for ECOin (Unix/GNU-Linux) #
|
||||
========================================
|
||||
|
||||
See doc/MINING.txt for detailed instructions on running /ecoin-miner/ on different platforms.
|
||||
|
||||
+ GNU/Linux:
|
||||
|
||||
cd miner/
|
||||
sh build.sh
|
||||
|
||||
An executable named 'cpuminer' will be built.
|
||||
|
||||
Now you can launch: ./cpuminer to run your ECOin PoW miner.
|
||||
|
||||
======================================
|
||||
|
||||
For a list of command-line options:
|
||||
|
||||
./ecoind --help
|
||||
|
||||
To start the ECOin daemon:
|
||||
|
||||
./ecoind -daemon
|
||||
|
||||
For ECOin-QT Wallet
|
||||
|
||||
./ecoin-qt
|
||||
|
||||
For debugging:
|
||||
|
||||
tail -f /home/$USER/.ecoin/debug.log
|
||||
|
||||
======================================
|
||||
|
||||
|
||||