') + ')', 'gi');
if (regex.test(text)) {
found = true;
var frag = document.createDocumentFragment();
var parts = text.split(regex);
parts.forEach(function(part, i) {
if (i % 2 === 0) {
frag.appendChild(document.createTextNode(part));
} else {
var span = document.createElement('span');
span.className = 'userscript-highlight';
span.textContent = part;
frag.appendChild(span);
}
});
node.parentNode.replaceChild(frag, node);
}
});
} else if (node.nodeType === 1 && node.childNodes) { // element
var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];
if (!skipTags.includes(node.tagName)) {
Array.from(node.childNodes).forEach(highlight);
}
}
}
highlight(document.body);
// Re-highlight on dynamic content
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
m.addedNodes.forEach(function(node) {
if (node.nodeType === 1 || node.nodeType === 3) highlight(node);
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); }
})();
(function(){
try {
var __m = "*";
var __re = new RegExp('^' + ".*" + ', 'i');
if (__m === '*' || __re.test(location.href)) {
// Strip utm_, fbclid, gclid, etc. from all links on page
(function() {
var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',
'ref', 'ref_src', 'source', 'medium', 'campaign'];
function cleanUrl(url) {
try {
var u = new URL(url, window.location.origin);
var changed = false;
trackingParams.forEach(function(p) {
if (u.searchParams.has(p)) {
u.searchParams.delete(p);
changed = true;
}
});
return changed ? u.toString() : url;
} catch (e) {
return url;
}
}
function cleanLinks() {
document.querySelectorAll('a[href]').forEach(function(a) {
var clean = cleanUrl(a.href);
if (clean !== a.href) a.href = clean;
});
}
cleanLinks();
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
m.addedNodes.forEach(function(node) {
if (node.nodeType === 1) {
if (node.tagName === 'A') cleanLinks();
node.querySelectorAll('a[href]').forEach(function(a) {
var clean = cleanUrl(a.href);
if (clean !== a.href) a.href = clean;
});
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); }
})();
(function(){
try {
var __m = "youtube.com";
var __re = new RegExp('^' + "youtube\\.com" + ', 'i');
if (__m === '*' || __re.test(location.href)) {
// Auto-enable theater mode on YouTube
(function() {
function tryTheater() {
var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]');
if (btn && !btn.classList.contains('activated')) {
btn.click();
}
}
// Try immediately
tryTheater();
// Try after navigation (SPA)
var lastUrl = location.href;
setInterval(function() {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(tryTheater, 500);
}
}, 1000);
// Also try on player load
var observer = new MutationObserver(tryTheater);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); }
})();
(function(){
try {
var __m = "*";
var __re = new RegExp('^' + ".*" + ', 'i');
if (__m === '*' || __re.test(location.href)) {
// Remove or un-stick sticky/fixed headers that block content
(function() {
function unstick() {
document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) {
if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {
el.style.position = 'static';
el.style.top = 'auto';
el.style.zIndex = 'auto';
}
});
}
unstick();
var observer = new MutationObserver(unstick);
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
})();
}
} catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); }
})();
})();
GitHub - AEIOUDev/OrRe_Web · GitHub
Repository files navigation 🏪 오리 가오리 - 원격 웨이팅 및 주문 관리 시스템 🙋 MyKnow: PM, FE, DevOps, QA Flutter Web을 WebAssembly(WASM)로 컴파일하여 경량화된 웹 프론트엔드를 구현했습니다. 내부 리눅스 서버에 자동 배포되는 CI/CD 파이프라인을 구축했습니다. 상태 동기화 및 WebSocket 연결 안정성을 확보하여 실시간성과 운영 안정성 모두를 만족시키는 구조를 설계하였습니다. QA 시나리오 수립 및 멀티 플랫폼 테스트를 통해 서비스 품질을 전반적으로 책임졌습니다. 오리 가오리 는 가게의 웨이팅과 주문을 원격으로 관리할 수 있도록 도와주는 서비스입니다.OrRe_Web은 Flutter로 구축된 웹 기반 웨이팅 서비스로, 비회원도 간편하게 대기열에 등록하고 매장 운영자는 실시간으로 웨이팅/주문 상황을 파악할 수 있도록 돕습니다. Flutter Web의 WASM 컴파일 방식을 통해 웹의 로딩 속도와 성능을 최적화하였으며, 자체 리눅스 서버에 자동 배포되도록 DevOps 환경을 설계했습니다.
✅ 사용자는 웹에서 대기 인원을 확인하고 사전 웨이팅을 등록할 수 있습니다. ✅ 회원이 아니더라도 매장 앞에서 QR 코드 또는 NFC 태그 를 이용해 웨이팅을 등록할 수 있습니다. ✅ 테이블마다 부착된 QR 코드/NFC 태그를 스캔하여 비대면 주문 을 진행할 수 있습니다. ✅ 인력 부담을 줄이고 운영 효율을 높일 수 있습니다. ✅ 점주는 웨이팅 및 주문을 한 곳에서 통합 관리 할 수 있습니다. ✅ 실시간으로 변동되는 데이터를 반영하여 손쉽게 매장을 운영할 수 있습니다.
🔗 원격 웨이팅 앱 오리, 가오리 시연 영상
✅ Flutter Web (WASM 빌드) ✅ Riverpod, Responsive UI, Custom Animation ✅ 자체 리눅스 서버 (Ubuntu) ✅ Nginx (정적 웹 서버 & Reverse Proxy) ✅ GitHub Actions 기반 CI/CD 구성 ✅ TLS 인증서 자동 갱신 (Let’s Encrypt + Certbot) 다양한 디바이스/브라우저에서 Flutter Web 렌더링 검증 브라우저별 동작 차이 분석 및 대응 (Chrome/Safari/Edge 등) WebSocket 연결 상태를 지속적으로 확인하는 헬스 체크 알고리즘 구현 연결 해제 시 HTTPS fallback 요청을 통해 서버 상태 점검 및 자동 재연결 실시간성이 중요한 웨이팅/주문 서비스 특성에 맞춰 장애 대응 로직 구현 🔥 2. Flutter Web의 WASM 배포 자동화 Flutter build web 시 --wasm 옵션으로 WASM 바이너리 생성 GitHub Actions 내에서 빌드 결과물을 .tar.gz로 압축 후 SCP를 통해 서버로 전송 리눅스 서버에서 자동 압축 해제 및 Nginx static 경로에 배포 배포 완료 후 자동 nginx reload + Cache Busting 처리 iOS Safari의 WASM 초기 로딩 문제 대응 사용자 경험에 영향을 주지 않도록 로딩 인디케이터 및 네트워크 예외처리 로직 강화
You can’t perform that action at this time.